Skip to content

Allow for secure LDAP password settings #5323

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

chriswhite199
Copy link
Contributor

@chriswhite199 chriswhite199 commented May 10, 2025

Description

  • Category: Enhancement
  • Why these changes are required: To allow for secure storage of LDAP password settings
  • What is the old behavior before changes and new behavior after changes
    • Before - settings for ldap password and pem_password were only configurable in an insecure manner (plaintext in yml or via cli / env variables)
    • After - Settings can now be acquired from the keystore (or still provided in an insecure manner, for which a warning will be shown in the logs, prompting the administrator to migrate to the secure versions)

If this PR is approved, i plan to submit similar PRs for the other secure settings using the SecurableLegacySetting class:

Issues Resolved

Do these changes introduce new permission(s) to be displayed in the static dropdown on the front-end: no

Testing

  • Unit tests updated for LDAP password (with insecure legacy setting and secure setting)
  • There is currently no existing test for pemkey_password

Check List

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: Chris White <chriswhite199@gmail.com>
Copy link

codecov bot commented May 10, 2025

Codecov Report

Attention: Patch coverage is 95.74468% with 2 lines in your changes missing coverage. Please review.

Project coverage is 71.75%. Comparing base (6e78dd9) to head (d33afd6).
Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
...org/opensearch/security/ssl/SecureSSLSettings.java 88.88% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #5323      +/-   ##
==========================================
+ Coverage   71.68%   71.75%   +0.07%     
==========================================
  Files         384      386       +2     
  Lines       23869    23877       +8     
  Branches     3647     3647              
==========================================
+ Hits        17111    17134      +23     
+ Misses       4928     4912      -16     
- Partials     1830     1831       +1     
Files with missing lines Coverage Δ
...security/auditlog/sink/ExternalOpenSearchSink.java 62.36% <ø> (ø)
...opensearch/security/auditlog/sink/WebhookSink.java 76.30% <ø> (ø)
...ty/auth/ldap/backend/LDAPAuthorizationBackend.java 68.54% <100.00%> (ø)
...earch/security/auth/ldap/util/ConfigConstants.java 100.00% <100.00%> (ø)
...urity/auth/ldap2/LDAPConnectionFactoryFactory.java 60.43% <100.00%> (ø)
...earch/security/setting/SecurableLegacySetting.java 100.00% <100.00%> (ø)
...ensearch/security/ssl/DefaultSecurityKeyStore.java 30.63% <ø> (ø)
...rch/security/ssl/config/SslCertificatesLoader.java 87.03% <100.00%> (ø)
...opensearch/security/ssl/util/SSLRequestHelper.java 66.98% <ø> (ø)
...ch/security/util/SettingsBasedSSLConfigurator.java 62.75% <ø> (ø)
... and 2 more

... and 7 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

chriswhite199 added a commit to chriswhite199/documentation-website that referenced this pull request May 10, 2025
See associated PR for implementation: opensearch-project/security#5323

Signed-off-by: Chris White <chriswhite199@gmail.com>
@cwperks
Copy link
Member

cwperks commented May 12, 2025

Thank you for this PR @chriswhite199 ! Its been on my agenda to support the keystore here for some time now :)

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.opensearch.security.ssl.SecureSSLSettings.SSLSetting.SECURITY_SSL_HTTP_PEMKEY_PASSWORD;

public class SecureSSLSettingsTest {
Copy link
Member

Choose a reason for hiding this comment

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

Should this class be renamed. It seems to only have 1 test now. It doesn't make sense to have this test class file.

}

@Test
public void testShouldFavorSecureOverInsecureSetting() {
Copy link
Member

Choose a reason for hiding this comment

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

👏

try {
return (SecurableLegacySetting) field.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to access field: " + field.getName(), e);
Copy link
Member

Choose a reason for hiding this comment

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

This should be SettingsException


/**
* Alternative to InsecureStringSetting, which doesn't raise an exception if allow_insecure_settings is false, but
* instead log.WARNs the violation. This is to appease a potential cyclic dependency between commons-utils
Copy link
Member

Choose a reason for hiding this comment

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

Interesting. For my curiosity, how did you discover this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't remember now, but setting allow_insecure_settings and defining the property as secure would raise an exception - hence the original implementation crafted two properties - password and password_secure to not break existing installations.

If you look at the original review for #2296, @peternied and I were discussing the integration test issues related to this - hopefully that comment can help you here

@cwperks
Copy link
Member

cwperks commented May 12, 2025

@chriswhite199 have you tested this outside of the unit tests? I think there may be larger structural changes required for settings in security yaml files to be able to fetch values from the keystore. For settings that reside in opensearch.yml this approach works, but I don't think that the security plugin currently has the ability to read from the keystore (unless I'm mistaken).

@chriswhite199
Copy link
Contributor Author

@chriswhite199 have you tested this outside of the unit tests? I think there may be larger structural changes required for settings in security yaml files to be able to fetch values from the keystore. For settings that reside in opensearch.yml this approach works, but I don't think that the security plugin currently has the ability to read from the keystore (unless I'm mistaken).

I have not yet (for authn / authz plugins, I know it works for tls certs for https / transport as this was tested in the original PR for that scope). I didn't think it would be any different for authNZ plugins but I will test and get back to you.

On a side not the DEVELOPING_WITH_DOCKER.md instructions didn't work for me under 3.0, something about a missing plugin.properties file (which could be related to the base image being opensearchstaging/opensearch, maybe that image doesn't have the security plugin unpacked correctly)

Copy link
Collaborator

@nibix nibix left a comment

Choose a reason for hiding this comment

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

Thank you for this. Secure settings for the security plugin are certainly an important topic.

Still, it feels to me that we have kind of a concept mismatch between the normal security settings (stored in an index) and the secure settings (stored in the opensearch-keystore file on each node).

  • It is unclear how the config names in security settings config.yml map to to the setting in the keystore, especially if we have lists of auth domains.
  • The processes of config hot reloading (without starting the node) seem to unaligned for security settings and secure settings (I think it is possible, but it will involve quite a few steps).

See below for more elaboration on that.

@@ -184,7 +184,7 @@ private ConnectionInitializer getConnectionInitializer() {
BindConnectionInitializer result = new BindConnectionInitializer();

String bindDn = settings.get(ConfigConstants.LDAP_BIND_DN, null);
String password = settings.get(ConfigConstants.LDAP_PASSWORD, null);
String password = ConfigConstants.LDAP_PASSWORD.getSetting(settings);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What would be the complete name of the secure setting for LDAP passwords?

In the security plugin, you can configure more than one LDAP authentication domain. This can then also have different passwords. If I get this right, we won't be able to configure more than one LDAP domain with different passwords, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The AuthenticationBackend settings are created from a subset of the properties here:

So it should allow for each individual backend to have it's own secure value. But examining the above only the settings from the index are used - there are no secure settings subset created and used when configuring the settings object for the backend.

Also a misunderstanding on my part renders this whole PR void, as the settings are centralized in an index, not pulled from a yaml on each node. We could allow for pulling the secure settings from the keystore by amending the above to subset and inject secure settings when creating the Settings for each backend, but then you'd almost certainly run into issues where the admin would have to remember to update the keystore on every node when secure values are set, and when changed (also leading to confusion if those keystores are not in sync - where auth make work against one node, but fail against another).

I'm leaning towards withdrawing this PR in light of the above, unless anyone has any ideas on a way forward?

Copy link
Member

Choose a reason for hiding this comment

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

@chriswhite199 I remember taking a look a few months ago when looking at #4004 and believe it will take some changes in OpenSearch core.

I'll take another look into what's required for the security config to support reading from the keystore and reply back ASAP. (It may take a few days).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe this is the direction the PR should take (in relation to #4004):

  • Amend the securityadmin script to allow for the passwords / secret settings to be sourced from the opensearch keystore (or config.yaml for legacy reasons)
  • securityadmin still stores the secrets in the .opendistro-security index

My original intent of this PR is to avoid having passwords in cleartext config files on the filesystem (currently an admin has to remember to remove the config.yaml or redact the passwords after syncing).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe it is a good idea to file an RFC about this.

We also have the trouble that there is not only securityadmin, but - optionally - the REST API can be also used to change/retrieve the config.yml configuration.

.put(ConfigConstants.LDAP_AUTHC_USERSEARCH, "(uid={0})")
.put(ConfigConstants.LDAP_AUTHC_USERBASE, "ou=people,o=TEST")
.put(ConfigConstants.LDAP_BIND_DN, "cn=Captain Spock,ou=people,o=TEST")
.setSecureSettings(mockSecureSettings)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we need one integration test for secure settings. You can find integration tests for LDAP here:

https://github.com/opensearch-project/security/blob/main/src/integrationTest/java/org/opensearch/security/http/LdapAuthenticationTest.java

Comment on lines +24 to +29
/**
* Wrapper for legacy settings that support a secure variant located in the Keystore.
* <p>
* Secure name is the insecure name with "_secure" appended to it.
*/
public class SecurableLegacySetting {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I am a bit confused about the word "legacy" here. Could you please elaborate what "legacy settings" are?

Comment on lines +80 to +86
return Arrays.stream(SecureSSLSettings.class.getDeclaredFields())
.filter(field -> SecurableLegacySetting.class.isAssignableFrom(field.getType()))
.map(field -> {
try {
return (SecurableLegacySetting) field.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to access field: " + field.getName(), e);
Copy link
Collaborator

Choose a reason for hiding this comment

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

It took me a while to understand this until I figured out that you are using reflection to retrieve static members (as a replacement for enum.values()).

As this is solution is maybe a bit non-obvious, I would recommend to add a comment somewhere to explain the approach.

Another question: With the SecurityManager, this would needed to be wrapped in a doPrivileged() block in order to be able to do reflection. With the new security manager replacement, did this need go away?

@DarshitChanpura
Copy link
Member

@chriswhite199 Are you still working on this PR?

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

Successfully merging this pull request may close these issues.

4 participants