spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.72k stars 5.86k forks source link

Spring Security with Active Directory shows *Property 'userDn' not set - anonymous context will be used for read-write operations* INFO message even if anonymous is disabled in HttpSecurity settings #14079

Closed dbnex14 closed 10 months ago

dbnex14 commented 10 months ago

Describe the bug I use AD to authenticate user which works as expected. I have class like below:

Note that I have enabled security debugging with@EnableWebSecurity(debug=true) in order to view security debugging information

@Configuration
@EnableWebSecurity(debug = true)  // ENABLE SECURITY DEBUGGING HERE
@EnableGlobalMethodSecurity(
    prePostEnabled = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Value("${ad.userDnPattern}")
    private String adUserDnPattern;

    @Value("${ad.url}")
    private String adUrl;

    @Value("${ad.domain}")
    private String adDomain;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(activeDirectoryAuthenticationProvider()).eraseCredentials(false);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .httpBasic()
            .and()
                .anyRequest().authenticated()
            .and()
                .anonymous().disable()  // DISABLE ANONYMOUS
                .formLogin().disable();
   }

    @Bean
    public AuthenticationProvider activeDirectoryAuthenticationProvider() {

        ActiveDirectoryLdapAuthenticationProvider ad = new ActiveDirectoryLdapAuthenticationProvider(adLdapDomain, adLdapUrl, adLdapUserDnPattern);
        ad.setConvertSubErrorCodesToExceptions(true);
        ad.setUseAuthenticationRequestCredentials(true);

        String adSearchFilter = "(&(sAMAccountName={1})(objectClass=user))";
        // String adSearchFilter = "(&(userPrincipalName={0})(objectClass=user))"; // SAME OUTCOME
        ad.setSearchFilter(adSearchFilter);

        return ad;
    }

   ...
}

Above, I set my searchFilter to use this AD filter using AMAccountName like (&(sAMAccountName={1})(objectClass=user)) (Same result happens if I use instead (&(userPrincipalName={0})(objectClass=user)) instead).

My authentication/authorization mechanism works as expected and I am very happy about it. However, with enabled security degugging (above @EnableWebSecurity(debug = true) ), I am seeing an INFO level message in the console when I run my application like this:

Property 'userDn' not set - anonymous context will be used for read-write operations

This tells me clearly that the application will allow users to access it anonymously without having to authenticate. Someone recommended to add line to HttpSecurity configuration above which I did (see line .anonymous().disable()) but this makes no difference.

I have spent days trying to figure out how to prevent this but I could not find any help on how to do this when connecting to AD.

To Reproduce Steps to reproduce the behavior.

  1. create basic Spring Boot API app
  2. add security
  3. configure security as above (I am using Spring Boot 2.7.x and cannot move to 3.x.x at the moment)
  4. Make sure to enable security debugging by annotating the security configuration with @EnableWebSecurity(debug = true) like above
  5. run your application and observe console output and you will see line Property 'userDn' not set - anonymous context will be used for read-write operations << PROBLEM

Expected behavior My understanding is that using &(sAMAccountName={1})and / or &(userPrincipalName={0})will plug in the registered AD userId of the user trying to authenticate, into the place holders {1} and / or {0} above. If user is present in AD, authentication will be successful. Otherwise, it will fail. I have exactly this behavior, so all good.

PROBLEM: I do not understand whay the INFO message says that anonymous context will be used for read-write operations. I do not want to use anonymous mode so I do not want that INFO message to appear since it is raising questions as we do not want to allow anonymous (meaning unknown users) to log in.

Sample

A link to a GitHub repository with a minimal, reproducible sample. I cannot provide access to our repositories, company policy.

Reports that include a sample will take priority over reports that do not. At times, we may require a sample, so it is good to try and include a sample up front.

marcusdacoregio commented 10 months ago

Hi, @dbnex14.

This tells me clearly that the application will allow users to access it anonymously without having to authenticate. Someone recommended to add line to HttpSecurity configuration above which I did (see line .anonymous().disable()) but this makes no difference.

I don't think that is right. If you take a look at the line that performs the log, you can see that it also set the anonymousReadOnly property to true. In the Spring LDAP documentation you can check what that property means:

Defines whether read-only operations are performed by using an anonymous (unauthenticated) context. Note that setting this parameter to true together with the compensating transaction support is not supported and is rejected.

It is important to note that you are not allowing anonymous users to log in, it only means that read-only operations will be performed using an anonymous context. If you want to specify the username (DN) of the "manager" user identity used to authenticate to an LDAP server, you can configure it like so:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(activeDirectoryAuthenticationProvider()).eraseCredentials(false);
    auth.ldapAuthentication().contextSource().managerDn("uid=admin,ou=system").managerPassword("mypassword")
}

You can also refer to the documentation for a more modern way of configuring LDAP authentication.

dbnex14 commented 10 months ago

Hi @marcusdacoregio and thank you for your reply.

Unfortunately, the answer and provided documentation links are contradicting themselves as I will try to explain below.

And to my point, a clear documentation in Spring showing how to auth using non-anonymous as well as anonymous way would help. I have spent days on this and all I can say, there is either no example explaining these 2 ways, of they are just debated in text but not in code examples therefore lacking clarity.

This makes problems in enterprises and I will try to explain below by replying to your comments, please follow

Hi, @dbnex14.

This tells me clearly that the application will allow users to access it anonymously without having to authenticate. Someone recommended to add line to HttpSecurity configuration above which I did (see line .anonymous().disable()) but this makes no difference.

I don't think that is right. If you take a look at the line that performs the log, you can see that it also set the anonymousReadOnly property to true. In the Spring LDAP documentation you can check what that property means:

Defines whether read-only operations are performed by using an anonymous (unauthenticated) context. Note that setting this parameter to true together with the compensating transaction support is not supported and is rejected.

I have to make 2 points on this:

  1. it says it sets anonymousReadOnly to true but then it says that setting this param to true is not supported and is rejected. It clearly says "yes, it is anonymous read-only set to true but my log INFO says it is actually anonymous read-write". Yet the explanation comment in the code says "Setting this property to true .... is not supported and will be rejected".
  2. setting anonymousReadOnly conflicts with the INFO message printed. The property name sugests that read-only is anonymous, but the log message says that "anonymous context will be used for read-write operations". So, one states it is read-only (but it also negates it as per 1 above) and the log message states it is actually not read-only but read-write and that anonymous context will be used. So, user is allowed to connect anonymously and use read-write operations to AD. This is what rings alarm bellw in enterprises as a potential security issue. I hope you understand by my points.

It is important to note that you are not allowing anonymous users to log in, it only means that read-only operations will be performed using an anonymous context.

Not exactly, my API does not allow anonymous user to to log in, It requires BasicAuth and sets anonymous.disabled(), I get that. But that is the API, not the AD. The AD context used is still anonymous as the LOG message clarly states and the message claims that AD context is anonymous for both read AND write, not only read.

If you want to specify the username (DN) of the "manager" user identity used to authenticate to an LDAP server, you can configure it like so:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(activeDirectoryAuthenticationProvider()).eraseCredentials(false);
    auth.ldapAuthentication().contextSource().managerDn("uid=admin,ou=system").managerPassword("mypassword")
}

To comment on this, setting managerDn like this assumes you have the uid and password available at the startup of the application, but that is not where authentication happens. This is just where AuthenticationManagerBuilder is set up, the actuall Authentication hapens in Authentication Filter (onSuccessfulAuthenticiation or onUnsucessfulAuthentication). So, it also does not apply because you do not authenticate 1 user only by supplying these values here, you authenticate once user provides their username/password in the BasicAuth header. That happens after the app has started and when user hists a protected endpoint requiring authentication. Finally, setting managerDn as above throws IllegalStateException: Embedded LDAP server is not provided. I am not using embedded LDAP, it is AD. So, I dont understand why setting managerDn requires embedded LDAP. In addition, you are saying to specify username (DN) of the "manager". Is this LDAP binding?

You can also refer to the documentation for a more modern way of configuring LDAP authentication.

This article is exactly the problem as I stated - no clarity. Take a look at it, it has only very small portion about Active Directory at the bottom and my code uses exactly that what is in this article. The rest of the article is totally unrelated because it uses embedded ldap AND because it is LDAP specific, not Active Directory specific. Given that you stated above to use userId (DN) "managerId"... this tells me that your article in this link (and pretty much all articles out there explaining Spring Security with AD) are using anonymous context for read-write. Because that is what it means if specifying userDN is required for non-anonymous authentication. Again, expanding that article and providing sections like "Anonymous authentication with Active Directory", "Non-anonymous Authentication with Active Directory" with examples would help.

I think this issue should be reopened as it is lacking clarity and explanation and this is raising eyebrows because once security debugging is enabled, it clearly logs a message that anonymous access in read-write form is allowed. No security team will go blind on this.

Please reopen to allow others to contribute and thank you.

dbnex14 commented 10 months ago
auth.ldapAuthentication().contextSource().managerDn("uid=admin,ou=system").managerPassword("mypassword")

So, I tried also setting this line in the AuthenticationManagerBuilder configuration in my code above. Regardless how I set it, I get same error back IllegalStateException: Embedded LDAP server is not provided. This seem to be only applicable if using Embeded LDAP but I am not using embedded LDAP, I am using AD.

dbnex14 commented 10 months ago

You can also refer to the documentation for a more modern way of configuring LDAP authentication.

I am using exactly what this documentation is recommending, see bottom of that page. It has only small portion for Active Directory. The rest of the article is unrelated as it mostly allies to the embedded LDAP. The small section at the very botton named "Active Directory contains very limited details (which I already use) and does not cover how to prevent INFO log message stating that anonymous context will be used for read-write operations . It appears that all article talking about Spring Security with AD, are using anonymous context.

marcusdacoregio commented 10 months ago

Hi, @dbnex14.

There is more documentation on the Spring LDAP Reference that you might want to check. Spring Security uses Spring LDAP under the hood, therefore everything that is not strictly related to authentication is in Spring LDAP docs.

To comment on this, setting managerDn like this assumes you have the uid and password available at the startup of the application, but that is not where authentication happens.

I do not think that you are following what that property means. The manager credentials are used for creating an authentication ContextSource.

Regardless how I set it, I get same error back IllegalStateException: Embedded LDAP server is not provided.

You are probably not setting the auth.ldapAuthentication().contextSource().url("your-ldap-url"). If it is not set, Spring Security will try to start an embedded LDAP server since there is no URL to connect.

It seems to me that the problem here is just that the log message is providing a misleading message, instead of read-write it should be read-only. Would you like to contribute with a PR that fixes the misleading message? Ideally, the PR should be on the Spring LDAP project and target the oldest supported branch, 2.4.x.

dbnex14 commented 10 months ago

Thank you Marcus but ...

Hi, @dbnex14.

There is more documentation on the Spring LDAP Reference that you might want to check. Spring Security uses Spring LDAP under the hood, therefore everything that is not strictly related to authentication is in Spring LDAP docs.

Yes, I checked that document as well. It mentions Active Directory only once and unrelated to the above reported issue. Again, I am talking about Active Directory LDAP which your documentation/examples lack context about. The above url is unrelated as it does not touch anything related to setting AD as described (very little and obscured way) in your documentation for example https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#_active_directory or https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/html/jc-authentication.html#java-ee-container-authentication and few other places.

To comment on this, setting managerDn like this assumes you have the uid and password available at the startup of the application, but that is not where authentication happens.

I do not think that you are following what that property means. The manager credentials are used for creating an authentication ContextSource.

Yes, but again, your documentation about Active Directory at the bottom of this document https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#_active_directory (and many more) does not use managerDN at all and if as you suggested I do set managerDN I get error about about "Embedded LDAP" which is not what I use. I am using AD.

Regardless how I set it, I get same error back IllegalStateException: Embedded LDAP server is not provided.

You are probably not setting the auth.ldapAuthentication().contextSource().url("your-ldap-url"). If it is not set, Spring Security will try to start an embedded LDAP server since there is no URL to connect.

Again, document about Active Directory use with Spring Security does not set this that way, see bottom https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#_active_directory. I am using exactly what your documentation states and also setting the AD LDAP url as the document suggests when using Spring Security with Active Directory. And in that case, the INFO log message shows that the anonymous context will be used for read-write operations if you enable security debug logging by use of @EnableWebSecurity(debug = true).

It seems to me that the problem here is just that the log message is providing a misleading message, instead of read-write it should be read-only. Would you like to contribute with a PR that fixes the misleading message? Ideally, the PR should be on the Spring LDAP project and target the oldest supported branch, 2.4.x.

The problem is that the was setting Active Directory with Spring Security appears to be always anonymous regardless is it read or read-write but yes read-write is even more problem. I am unable to contribute as this is eating to much of my time but I think it is enough contribution already what I provided here. It appears clear that there is a disconnect when it commes with Spring Security and Active Directory documentation. I could only suggest how to improve, which in my opinion would be to:

Update the Spring Security Active Directory section of the above document to provide

  1. clear explanation what anonymous mean
  2. example of non-anonymous authentication with AD (since what you have in the documents appears to be anonymous according to the log message)
  3. to make it even better, provide also anonymous authentication with AD example
  4. fix the log message - as by looking at your code - it conflicts with its trigger point (read vs read-write)
  5. Your documentation states in several places that AD is different than standard LDAP and that it is "self-contained" - explain what it means
  6. Same I notice is use for "manager" (with quotation mark) - what does it even mean? Manager but not manager? Kind of manager?, Department manager?, ....
  7. Also, this documentation https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#_active_directory uses EmbeddedLdap to demo how LDAP security works. No one will ever use embedded Ldap in enterprise but rather Active Directory, Tivoli, Oracle Unified Directory etc), so embeddedLdap is rather a learning tool rather than something used in enterprises. Yet all Spring Boot documents talk about EmbeddedLdap.

image

marcusdacoregio commented 10 months ago

Hi, @dbnex14.

I apologize for the confusion in this issue and I appreciate your effort in researching via the documentation. We are willing to update the documentation to make your use case easier to apply.

Related to the log message, I opened https://github.com/spring-projects/spring-ldap/issues/833 to check that on the Spring LDAP side.

It seems to me that this ticket could be focused on improving the documentation around supported Active Directory configurations. It would be great if you could share a sample application or even submit a PR yourself that enhances the docs. In addition to that, I'll bring this to the team's attention and collect some more information to improve the docs.

I'd like more clarification on the following:

This tells me clearly that the application will allow users to access it anonymously without having to authenticate. Someone recommended to add line to HttpSecurity configuration above which I did (see line .anonymous().disable()) but this makes no difference.

What do you mean by accessing it anonymously? Is it that you have a protected endpoint and you are afraid that it could be accessed without authentication? I don't see how .anonymous().disable() is related to that.

dbnex14 commented 10 months ago

Thanks you @marcusdacoregio ,

please see below...

Hi, @dbnex14.

I apologize for the confusion in this issue and I appreciate your effort in researching via the documentation. We are willing to update the documentation to make your use case easier to apply.

Related to the log message, I opened spring-projects/spring-ldap#833 to check that on the Spring LDAP side.

It seems to me that this ticket could be focused on improving the documentation around supported Active Directory configurations. It would be great if you could share a sample application or even submit a PR yourself that enhances the docs. In addition to that, I'll bring this to the team's attention and collect some more information to improve the docs.

I will see if I get a chance to create a quick dummy project and share with you but the thing is really simple, just use your documentation from above comment, annotate the spring security class with @EnableWebSecurity(debug = true) annotation to enable security debugging and when you start up your app, watch the console log and you will see message like the anonymous context will be used for read-write operations . I will see if I get some time to create a very simple RESTAPI and share.

I'd like more clarification on the following:

This tells me clearly that the application will allow users to access it anonymously without having to authenticate. Someone recommended to add line to HttpSecurity configuration above which I did (see line .anonymous().disable()) but this makes no difference.

What do you mean by accessing it anonymously? Is it that you have a protected endpoint and you are afraid that it could be accessed without authentication? I don't see how .anonymous().disable() is related to that.

No, ignore this please. As I said my API is handling the authentication and authorization, so no one can access it anonymously. The sole problem I am bringing here is the above log message and the fact that Spring documentation is lacking context when it comes to Active Directory:

  1. what exactly is considered anonymous context - from the reading I did, it is nothing reasuring and for a reason concerning to see that message in logs
  2. how do you set up AD configuration so it is non-anonymous? From the documentation and screenshot I provided above, that will always show that it is anonymous context and that is not good
  3. for better understanding by readers, it would be good to also provide situation where configuration is anonymous, it would help us better understand but again, from what I see, that is what you currently provide (screenshot above).

I feel your documentation is way to focused on embeded LDAP which no one will use really except for learning, then to standard LDAPs, and almost nothing about Active Directory.

Thanks again, I appreciate your help and effort

dbnex14 commented 10 months ago

@marcusdacoregio Here is a very basic demo project using ADLdap demo. As you can see, it is setting all. The 3 application.properties for LDAP are not set to a real AD LDAP, but you dont even need it. Main think spring security debugging is enabled, see annotation in SecurityConfig class.

Once you issue mvn clean install or run the application, the console output will show the INFO log message

image

Property 'userDn' not set - anonymous context will be used for read-write operations

https://github.com/dbnex14/ADLDAPDemo/blob/master/HELP.md

Why is this problem and what is causing it?

I hope the screenshot below showing spring-ldap AbstractContextSource.java explains it

image

marcusdacoregio commented 10 months ago

You do not need AD configuration or web security debug enabled for that log message to appear. If you have some common configuration with embedded LDAP server like the following:

@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
    LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
    factory.setUserDnPatterns("uid={0},ou=people");
    return factory.createAuthenticationManager();
}

@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
    EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
            EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
    contextSourceFactoryBean.setPort(0);
    contextSourceFactoryBean.setLdif("classpath:users.ldif");
    return contextSourceFactoryBean;
}

Then you will see:

2023-11-06T09:52:56.735-03:00  INFO 21932 --- [           main] s.s.l.DefaultSpringSecurityContextSource : Configure with URL ldap://127.0.0.1:52161/dc=springframework,dc=org and root DN dc=springframework,dc=org
2023-11-06T09:52:56.736-03:00  INFO 21932 --- [           main] o.s.l.c.support.AbstractContextSource    : Property 'userDn' not set - anonymous context will be used for read-write operations

Related to that, there is an issue open in Spring LDAP to fix the misleading message.

The userDn in AbstractContextSource, which is mapped by managerDn in Spring Security LDAP configuration, is the username (principal) to use when authenticating with the LDAP server (docs). This is usually the distinguished name of an admin user (for example, cn=Administrator) but may differ depending on server and authentication method. When you do not specify such credentials, the AbstractContextSource will use an AuthenticationSource with null for both principal and credentials. When you use an anonymous context for LDAP, it means that you're attempting to access the LDAP directory without providing any credentials or authentication information. In other words, you're making LDAP requests without identifying yourself as a specific user or administrator. Depending on the LDAP server's configuration, anonymous access may be restricted to read-only operations or may not be allowed at all. In many cases, anonymous access only allows you to search and retrieve information from the directory but doesn't grant you permission to modify or add entries.

Note that the manager credentials are not the username and password of the currently authenticated user. However, you can specify a custom principal to create the authenticated Context (documented here), which could be the currently logged-in user principal.

PROBLEM: I do not understand whay the INFO message says that anonymous context will be used for read-write operations. I do not want to use anonymous mode so I do not want that INFO message to appear since it is raising questions as we do not want to allow anonymous (meaning unknown users) to log in.

This concern will be taken care of by https://github.com/spring-projects/spring-ldap/issues/833. If you do not want to use an anonymous context, you should provide the manager credentials as mentioned above and here. You can also opt into disabling the LDAP auto configuration by doing @SpringBootApplication(exclude = LdapAutoConfiguration.class), since that is the configuration that creates the LdapContextSource and the ActiveDirectoryLdapAuthenticationProvider does not need a ContextSource.

That being said, we are looking into how to improve the documentation to include the necessary information about all that.

marcusdacoregio commented 10 months ago

Closing in favor of https://github.com/spring-projects/spring-ldap/issues/833

dbnex14 commented 10 months ago

The userDn in AbstractContextSource, which is mapped by managerDn in Spring Security LDAP configuration, is the username (principal) to use when authenticating with the LDAP server (docs). This is usually the distinguished name of an admin user (for example, cn=Administrator) but may differ depending on server and authentication method. When you do not specify such credentials, the AbstractContextSource will use an AuthenticationSource with null for both principal and credentials. When you use an anonymous context for LDAP, it means that you're attempting to access the LDAP directory without providing any credentials or authentication information. In other words, you're making LDAP requests without identifying yourself as a specific user or administrator. Depending on the LDAP server's configuration, anonymous access may be restricted to read-only operations or may not be allowed at all. In many cases, anonymous access only allows you to search and retrieve information from the directory but doesn't grant you permission to modify or add entries.

Note that the manager credentials are not the username and password of the currently authenticated user. However, you can specify a custom principal to create the authenticated Context (documented here), which could be the currently logged-in user principal.

@marcusdacoregio This would be excellent addition to the documentation I was referring above. What really struck me in Spring Security documentation about this usage of "manager" (with quotation marks). As I said earlier, what does it even mean? Also usage of anonymous without explaining what does it exactly mean. Lots of documentation out there say simply anonymous users can do whatever they want. it would be great to have docs update this. Much appreciated

dbnex14 commented 8 months ago

@marcusdacoregio Thanks Marcus, this line explained lot "You can also opt into disabling the LDAP auto configuration by doing @SpringBootApplication(exclude = LdapAutoConfiguration.class), since that is the configuration that creates the LdapContextSource and the ActiveDirectoryLdapAuthenticationProvider does not need a ContextSource.".

... but if you do this on application level (with @SpringBootApplicationannotation) you are disabling it for entire application. This is OK if your application is authenticating only against AD which as you explained in the documentation is quite different than standard LDAP and is "self-contained".

However, and it is a common scenarios that an API might have to authenticate against multiple LDAPs. One of which might be AD but the 2nd one might be Oracle Unified Directory or IBM Tivoli or some other LDAP vendor. Wo, while opting out of it by use of @SpringBootApplication(exclude = LdapAutoConfiguration.class)will solve the issue in AD, it will create issue in other "standard" LDAP implementation like for example Oracle Unified Directory or which ever, which do not need to opt out of LdapAutoConfiguration.class.

For example, I use in addition to AD, Oracle Unified Directory LDAP. I have separete class handling Oracle Unified Directory LDAP configuration which will setup LdapContextSourcelike

    private void initLdapContext() {
        LdapContextSource ldapContextSource = new LdapContextSource();
        ldapContextSource.setUrl(ldapUrl);
        ldapContextSource.setAnonymousReadOnly(true);
        ldapContextSource.setUserDn("uid={0}," + ldapUserDnPattern);
        ldapContextSource.setBase("o=internet");
        ldapContextSource.afterPropertiesSet();

        ldapTemplate = new LdapTemplate(ldapContextSource);
    }

I wonder if there is a way to opt in/out of using LdapAutoConfigurationon a finer level rather than on entire application?

Thank you