Skip to content
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

Secure password for monitoring HTTP exporter #50919

Merged
merged 24 commits into from
Jan 30, 2020

Conversation

danhermann
Copy link
Contributor

@danhermann danhermann commented Jan 13, 2020

Adds reloadable SECURE_AUTH_PASSWORD to allow keystore entries in the form xpack.monitoring.exporters.*.secure_auth_password to securely supply passwords for monitoring exporters. Deprecates the insecure AUTH_PASSWORD setting.

It would have been ideal to include validation that only one of either the AUTH_PASSWORD or SECURE_AUTH_PASSWORD were set but the settings validation framework does not support secure settings. Additionally, the validation on the AUTH_USERNAME setting that required a password to be set if the username was set had to be removed because the password can now be set via a secure setting that the validation framework cannot see. It might be possible to extend the validation framework to handle secure settings, but if so, that would definitely be a separate work effort.

Making the SECURE_AUTH_PASSWORD setting reloadable meant that the Monitoring plugin had to be able to trigger a reload of any HTTP exporters when the cluster received a _nodes/reload_secure_settings request. This necessitated (unless I missed a cleaner approach) the addition of a reference to Exporters within the Monitoring class.

Fixes #50197.

@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-features (:Core/Features/Monitoring)

@danhermann
Copy link
Contributor Author

In its draft state, the password is securely read and supplied to the exporter, but any attempt to dynamically change any other monitoring settings such as xpack.monitoring.exporters.*.enabled results in an error setting [xpack.monitoring.exporters.*.secure_auth_password] is not dynamic though it is neither designated nor intended to be dynamic.

@danhermann danhermann added the WIP label Jan 13, 2020
@albertzaharovits
Copy link
Contributor

@danhermann The reason for the error, is that there's an assumption that all HTTP exporter settings are dynamic, but you've just added one that isn't. Take a look at:

clusterService.getClusterSettings().addSettingsUpdateConsumer(this::setExportersSetting, getSettings());

You'll have to exclude the reloadable Secure setting from the list that an updater is registered, as well as have a way to furnish the cached keystore values to the initExporters method

@danhermann
Copy link
Contributor Author

@elasticmachine update branch

@danhermann danhermann removed the WIP label Jan 15, 2020
@danhermann danhermann marked this pull request as ready for review January 15, 2020 03:37
@danhermann
Copy link
Contributor Author

@elasticmachine update branch

@danhermann
Copy link
Contributor Author

@elasticmachine update branch

@@ -401,6 +405,7 @@ public void validate(String password, Map<Setting<?>, Object> settings) {
*/
private final AtomicBoolean clusterAlertsAllowed = new AtomicBoolean(false);

private static final Map<String, String> SECURE_AUTH_PASSWORDS = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Passwords as String should be avoided where possible due to help avoid leaking to heap since gc happens when ever it feel like it. I think you can use SecureString instead and then close it out to ensure it zero'd out when you are done.

Also since it looks like you are using this for equality, can you just hash it and store the hash in the map ?

Copy link
Contributor Author

@danhermann danhermann Jan 22, 2020

Choose a reason for hiding this comment

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

I could change it to a Map<String, SecureString> but I couldn't close any of the SecureString instances because the HTTP exporter has to be recreated (so I need the full value, not just an equality check) any time any of its settings, whether secure or not, change. Unfortunately, this is unavoidable for settings that are both secure and reloadable since the keystore itself is not accessible outside of node startup and reload events. The security folks directed me to

* Extracts the {@link SecureSettings}` out of the passed in {@link Settings} object. The {@code Setting} argument has to have the
* {@code SecureSettings} open/available. Normally {@code SecureSettings} are available only under specific callstacks (eg. during node
* initialization or during a `reload` call). The returned copy can be reused freely as it will never be closed (this is a bit of
* cheating, but it is necessary in this specific circumstance). Only works for secure settings of type string (not file).
*
* @param source
* A {@code Settings} object with its {@code SecureSettings} open/available.
* @param securePluginSettings
* The list of settings to copy.
* @return A copy of the {@code SecureSettings} of the passed in {@code Settings} argument.
*/
private static SecureSettings extractSecureSettings(Settings source, List<Setting<?>> securePluginSettings)
throws GeneralSecurityException {
// get the secure settings out
final SecureSettings sourceSecureSettings = Settings.builder().put(source, true).getSecureSettings();
// filter and cache them...
final Map<String, Tuple<SecureString, byte[]>> cache = new HashMap<>();
if (sourceSecureSettings != null && securePluginSettings != null) {
for (final String settingKey : sourceSecureSettings.getSettingNames()) {
for (final Setting<?> secureSetting : securePluginSettings) {
if (secureSetting.match(settingKey)) {
cache.put(settingKey,
new Tuple<>(sourceSecureSettings.getString(settingKey), sourceSecureSettings.getSHA256Digest(settingKey)));
}
}
}
}
return new SecureSettings() {
@Override
public boolean isLoaded() {
return true;
}
@Override
public SecureString getString(String setting) {
return cache.get(setting).v1();
}
@Override
public Set<String> getSettingNames() {
return cache.keySet();
}
@Override
public InputStream getFile(String setting) {
throw new IllegalStateException("A NotificationService setting cannot be File.");
}
@Override
public byte[] getSHA256Digest(String setting) {
return cache.get(setting).v2();
}
@Override
public void close() throws IOException {
}
};
}

where they did essentially the same thing for the secure settings in watcher notifications.

final List<String> changedExporters = new ArrayList<>();
for (final String namespace : SECURE_AUTH_PASSWORD_SETTING.getNamespaces(settings)) {
final Setting<?> s = SECURE_AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace);
final String password = s.get(settings).toString();
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto about SecureString

docs/reference/settings/monitoring-settings.asciidoc Outdated Show resolved Hide resolved
if (useBasicAuth) {
assertWarnings("[xpack.monitoring.exporters._http.auth.password] setting was deprecated in Elasticsearch and will be " +
"removed in a future release! See the breaking changes documentation for the next major version.");
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add some unit tests for changing the secure setting and ensure it is changed (or no-op) as well as trying to set this for a non-http type setting ? See org.elasticsearch.xpack.watcher.notification.NotificationService for some examples.

Ideally, we would also have Integration tests, but I didn't see any framework already setup to read from the keystore for a Integration tests (and an almost complete lack of Rest tests) :(

@danhermann
Copy link
Contributor Author

@elasticmachine update branch

@danhermann
Copy link
Contributor Author

@elasticmachine run elasticsearch-ci/docs

@danhermann
Copy link
Contributor Author

@jakelandis, I think this one is ready for another look.

Copy link
Contributor

@jakelandis jakelandis left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for the updates and test

Co-Authored-By: Lisa Cawley <lcawley@elastic.co>
@danhermann
Copy link
Contributor Author

@jakelandis, sorry for changing this after your review -- it occurred to me that because we cannot validate that the user supplied only one password, we should at least log a warning if that happens so we are not silently ignoring the value of auth.password if auth.secure_password is also specified. I added that logging in a7974e7. If you concur, I will merge this. There were also a couple doc fix commits that the doc folks reviewed.

@jakelandis
Copy link
Contributor

@danhermann - no problem, additions look good.

LGTM2

@danhermann danhermann merged commit dd8add6 into elastic:master Jan 30, 2020
danhermann added a commit to danhermann/elasticsearch that referenced this pull request Jan 31, 2020
Adds a secure and reloadable SECURE_AUTH_PASSWORD setting to allow keystore entries in the form "xpack.monitoring.exporters.*.auth.secure_password" to securely supply passwords for monitoring HTTP exporters. Also deprecates the insecure `AUTH_PASSWORD` setting.
@danhermann danhermann deleted the 50197_secure_password branch March 12, 2020 12:40
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.

Allow monitoring HTTP exporter password to be stored in keystore
6 participants