Skip to content

Secure password for monitoring HTTP exporter #50919

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 24 commits into from
Jan 30, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
11f3d34
Secure monitoring password
danhermann Dec 18, 2019
13a1a3e
fix tests
danhermann Jan 14, 2020
d8bd711
Merge branch 'master' into 50197_secure_password
elasticmachine Jan 14, 2020
79e0267
Merge branch 'master' into 50197_secure_password
elasticmachine Jan 14, 2020
322e690
making setting reloadable
danhermann Jan 15, 2020
ce03cd1
Merge branch 'master' into 50197_secure_password
elasticmachine Jan 15, 2020
25f9515
fix reloading and test
danhermann Jan 15, 2020
da29f4e
Merge branch 'master' into 50197_secure_password
elasticmachine Jan 15, 2020
a0e8064
handle deprecation warnings in integration tests
danhermann Jan 15, 2020
4840a0f
checkstyle
danhermann Jan 15, 2020
99811a2
accommodate either password in validation
danhermann Jan 17, 2020
ceb13b3
switch to ConcurrentHashMap
danhermann Jan 22, 2020
e158752
add comment about validation
danhermann Jan 22, 2020
15624ab
Merge branch 'master' into 50197_secure_password
elasticmachine Jan 22, 2020
e6be346
remove obsolete test
danhermann Jan 23, 2020
b8f8cbe
integration test that reloads secure password
danhermann Jan 24, 2020
8f9c13c
Merge branch 'master' into 50197_secure_password
elasticmachine Jan 24, 2020
c625867
rename setting and update docs
danhermann Jan 25, 2020
9e3a591
add new tests
danhermann Jan 27, 2020
a7974e7
log warning if both secure password and deprecated password are supplied
danhermann Jan 28, 2020
1252a8a
Update docs/reference/settings/monitoring-settings.asciidoc
danhermann Jan 29, 2020
9019ab5
Update docs/reference/settings/monitoring-settings.asciidoc
danhermann Jan 29, 2020
5f97171
add link from reloadable settings page to monitoring settings page
danhermann Jan 29, 2020
3ccd9e9
update docs
danhermann Jan 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions docs/reference/settings/monitoring-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ For more information, see <<monitor-elasticsearch-cluster>>.
==== General Monitoring Settings

`xpack.monitoring.enabled`::
Set to `true` (default) to enable {es} {monitoring} for {es} on the node.
Set to `true` (default) to enable {es} {monitoring} for {es} on the node.
+
--
NOTE: To enable data collection, you must also set `xpack.monitoring.collection.enabled`
to `true`. Its default value is `false`.
NOTE: To enable data collection, you must also set `xpack.monitoring.collection.enabled`
to `true`. Its default value is `false`.
--

[float]
Expand All @@ -51,7 +51,7 @@ this setting is `false` (default), {es} monitoring data is not collected and
all monitoring data from other sources such as {kib}, Beats, and Logstash is
ignored.

`xpack.monitoring.collection.interval` (<<cluster-update-settings,Dynamic>>)::
`xpack.monitoring.collection.interval` (<<cluster-update-settings,Dynamic>>)::

Setting to `-1` to disable data collection is no longer supported beginning with
7.0.0. deprecated[6.3.0, Use `xpack.monitoring.collection.enabled` set to `false` instead.]
Expand Down Expand Up @@ -198,11 +198,16 @@ xpack.monitoring.exporters:

`auth.username`::

The username is required if a `auth.password` is supplied.
The username is required if `auth.secure_password` or `auth.password` is supplied.

`auth.secure_password` ({ref}/secure-settings.html[Secure], {ref}/secure-settings.html#reloadable-secure-settings[reloadable])::

The password for the `auth.username`. Takes precedence over `auth.password` if it is also specified.

`auth.password`::

The password for the `auth.username`.
The password for the `auth.username`. This setting is ignored if `auth.secure_password` is also specified. This setting is
deprecated[7.7.0, Use `auth.secure_password` instead.].

`connection.timeout`::

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ public MockRequest takeRequest() {
return requests.poll();
}

/**
* Removes all requests from the queue.
*/
public void clearRequests() {
requests.clear();
}

/**
* A utility method to peek into the requests and find out if #MockWebServer.takeRequests will not throw an out of bound exception
* @return true if more requests are available, false otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.ReloadablePlugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.script.ScriptService;
Expand Down Expand Up @@ -68,7 +69,7 @@
import static java.util.Collections.singletonList;
import static org.elasticsearch.common.settings.Setting.boolSetting;

public class Monitoring extends Plugin implements ActionPlugin {
public class Monitoring extends Plugin implements ActionPlugin, ReloadablePlugin {

/**
* The ability to automatically cleanup ".watcher_history*" indices while also cleaning up Monitoring indices.
Expand All @@ -80,6 +81,8 @@ public class Monitoring extends Plugin implements ActionPlugin {
protected final Settings settings;
private final boolean enabled;

private Exporters exporters;

public Monitoring(Settings settings) {
this.settings = settings;
this.enabled = XPackSettings.MONITORING_ENABLED.get(settings);
Expand Down Expand Up @@ -110,8 +113,7 @@ public Collection<Object> createComponents(Client client, ClusterService cluster
Map<String, Exporter.Factory> exporterFactories = new HashMap<>();
exporterFactories.put(HttpExporter.TYPE, config -> new HttpExporter(config, dynamicSSLService, threadPool.getThreadContext()));
exporterFactories.put(LocalExporter.TYPE, config -> new LocalExporter(config, client, cleanerService));
final Exporters exporters = new Exporters(settings, exporterFactories, clusterService, getLicenseState(),
threadPool.getThreadContext());
exporters = new Exporters(settings, exporterFactories, clusterService, getLicenseState(), threadPool.getThreadContext());

Set<Collector> collectors = new HashSet<>();
collectors.add(new IndexStatsCollector(clusterService, getLicenseState(), client));
Expand Down Expand Up @@ -178,4 +180,13 @@ public List<String> getSettingsFilter() {
final String exportersKey = "xpack.monitoring.exporters.";
return List.of(exportersKey + "*.auth.*", exportersKey + "*.ssl.*");
}

@Override
public void reload(Settings settings) throws Exception {
final List<String> changedExporters = HttpExporter.loadSettings(settings);
for (String changedExporter : changedExporters) {
final Settings settingsForChangedExporter = settings.filter(x -> x.startsWith("xpack.monitoring.exporters." + changedExporter));
exporters.setExportersSetting(settingsForChangedExporter);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.elasticsearch.xpack.core.monitoring.exporter.MonitoringDoc;
import org.elasticsearch.xpack.monitoring.collector.Collector;
import org.elasticsearch.xpack.monitoring.exporter.Exporters;
import org.elasticsearch.xpack.monitoring.exporter.http.HttpExporter;

import java.io.Closeable;
import java.util.ArrayList;
Expand Down Expand Up @@ -104,6 +105,8 @@ public class MonitoringService extends AbstractLifecycleComponent {
.addSettingsUpdateConsumer(ELASTICSEARCH_COLLECTION_ENABLED, this::setElasticsearchCollectionEnabled);
clusterService.getClusterSettings().addSettingsUpdateConsumer(ENABLED, this::setMonitoringActive);
clusterService.getClusterSettings().addSettingsUpdateConsumer(INTERVAL, this::setInterval);

HttpExporter.loadSettings(settings);
}

void setElasticsearchCollectionEnabled(final boolean enabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static java.util.Collections.emptyMap;

Expand All @@ -58,15 +59,17 @@ public Exporters(Settings settings, Map<String, Exporter.Factory> factories,
this.clusterService = Objects.requireNonNull(clusterService);
this.licenseState = Objects.requireNonNull(licenseState);

clusterService.getClusterSettings().addSettingsUpdateConsumer(this::setExportersSetting, getSettings());
final List<Setting.AffixSetting<?>> dynamicSettings =
getSettings().stream().filter(Setting::isDynamic).collect(Collectors.toList());
clusterService.getClusterSettings().addSettingsUpdateConsumer(this::setExportersSetting, dynamicSettings);
HttpExporter.registerSettingValidators(clusterService);
// this ensures, that logging is happening by adding an empty consumer per affix setting
for (Setting.AffixSetting<?> affixSetting : getSettings()) {
// this ensures that logging is happening by adding an empty consumer per affix setting
for (Setting.AffixSetting<?> affixSetting : dynamicSettings) {
clusterService.getClusterSettings().addAffixUpdateConsumer(affixSetting, (s, o) -> {}, (s, o) -> {});
}
}

private void setExportersSetting(Settings exportersSetting) {
public void setExportersSetting(Settings exportersSetting) {
if (this.lifecycle.started()) {
Map<String, Exporter> updated = initExporters(exportersSetting);
closeExporters(logger, this.exporters.getAndSet(updated));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.SecureSetting;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
Expand Down Expand Up @@ -52,6 +54,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
Expand Down Expand Up @@ -206,23 +209,20 @@ public void validate(final String username, final Map<Setting<?>, Object> settin
final String namespace =
HttpExporter.AUTH_USERNAME_SETTING.getNamespace(
HttpExporter.AUTH_USERNAME_SETTING.getConcreteSetting(key));
final String password =
(String) settings.get(AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace));

// password must be specified along with username for any auth
if (Strings.isNullOrEmpty(username) == false) {
if (Strings.isNullOrEmpty(password)) {
throw new SettingsException(
"[" + AUTH_USERNAME_SETTING.getConcreteSettingForNamespace(namespace).getKey() + "] is set " +
"but [" + AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace).getKey() + "] is " +
"missing");
}
final String type =
(String) settings.get(Exporter.TYPE_SETTING.getConcreteSettingForNamespace(namespace));
if ("http".equals(type) == false) {
throw new SettingsException("username for [" + key + "] is set but type is [" + type + "]");
}
}

// it would be ideal to validate that just one of either AUTH_PASSWORD_SETTING or
// AUTH_SECURE_PASSWORD_SETTING were present here, but that is not currently possible with the settings
// validation framework.
// https://github.com/elastic/elasticsearch/issues/51332
}

@Override
Expand All @@ -232,8 +232,7 @@ public Iterator<Setting<?>> settings() {
HttpExporter.AUTH_USERNAME_SETTING.getConcreteSetting(key));

final List<Setting<?>> settings = List.of(
Exporter.TYPE_SETTING.getConcreteSettingForNamespace(namespace),
HttpExporter.AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace));
Exporter.TYPE_SETTING.getConcreteSettingForNamespace(namespace));
return settings.iterator();
}

Expand Down Expand Up @@ -285,8 +284,18 @@ public Iterator<Setting<?>> settings() {
},
Property.Dynamic,
Property.NodeScope,
Property.Filtered),
Property.Filtered,
Property.Deprecated),
TYPE_DEPENDENCY);
/**
* Secure password for basic auth.
*/
public static final Setting.AffixSetting<SecureString> AUTH_SECURE_PASSWORD_SETTING =
Setting.affixKeySetting(
"xpack.monitoring.exporters.",
"auth.secure_password",
key -> SecureSetting.secureString(key, null),
TYPE_DEPENDENCY);
/**
* The SSL settings.
*
Expand Down Expand Up @@ -401,6 +410,7 @@ public Iterator<Setting<?>> settings() {
*/
private final AtomicBoolean clusterAlertsAllowed = new AtomicBoolean(false);

private static final ConcurrentHashMap<String, SecureString> SECURE_AUTH_PASSWORDS = new ConcurrentHashMap<>();
private final ThreadContext threadContext;
private final DateFormatter dateTimeFormatter;

Expand Down Expand Up @@ -689,6 +699,25 @@ private static void configureTimeouts(final RestClientBuilder builder, final Con
builder.setRequestConfigCallback(new TimeoutRequestConfigCallback(connectTimeout, socketTimeout));
}


/**
* Caches secure settings for use when dynamically configuring HTTP exporters
* @param settings settings used for configuring HTTP exporter
* @return names of HTTP exporters whose secure settings changed, if any
*/
public static List<String> loadSettings(Settings settings) {
final List<String> changedExporters = new ArrayList<>();
for (final String namespace : AUTH_SECURE_PASSWORD_SETTING.getNamespaces(settings)) {
final Setting<SecureString> s = AUTH_SECURE_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace);
final SecureString securePassword = s.get(settings);
final SecureString existingPassword = SECURE_AUTH_PASSWORDS.put(namespace, securePassword);
if (securePassword.equals(existingPassword) == false) {
changedExporters.add(namespace);
}
}
return changedExporters;
}

/**
* Creates the optional {@link CredentialsProvider} with the username/password to use with <em>all</em> requests for user
* authentication.
Expand All @@ -700,7 +729,19 @@ private static void configureTimeouts(final RestClientBuilder builder, final Con
@Nullable
private static CredentialsProvider createCredentialsProvider(final Config config) {
final String username = AUTH_USERNAME_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings());
final String password = AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings());

final String deprecatedPassword = AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings());
final SecureString securePassword = SECURE_AUTH_PASSWORDS.get(config.name());
final String password;
if (securePassword != null) {
password = securePassword.toString();
if (Strings.isNullOrEmpty(deprecatedPassword) == false) {
logger.warn("exporter [{}] specified both auth.secure_password and auth.password. using auth.secure_password and " +
"ignoring auth.password", config.name());
}
} else {
password = deprecatedPassword;
}

final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
Expand Down Expand Up @@ -865,9 +906,19 @@ public void doClose() {
}
}

public static List<Setting.AffixSetting<?>> getSettings() {
public static List<Setting.AffixSetting<?>> getDynamicSettings() {
return Arrays.asList(HOST_SETTING, TEMPLATE_CREATE_LEGACY_VERSIONS_SETTING, AUTH_PASSWORD_SETTING, AUTH_USERNAME_SETTING,
BULK_TIMEOUT_SETTING, CONNECTION_READ_TIMEOUT_SETTING, CONNECTION_TIMEOUT_SETTING, PIPELINE_CHECK_TIMEOUT_SETTING,
PROXY_BASE_PATH_SETTING, SNIFF_ENABLED_SETTING, TEMPLATE_CHECK_TIMEOUT_SETTING, SSL_SETTING, HEADERS_SETTING);
}

public static List<Setting.AffixSetting<?>> getSecureSettings() {
return List.of(AUTH_SECURE_PASSWORD_SETTING);
}

public static List<Setting.AffixSetting<?>> getSettings() {
List<Setting.AffixSetting<?>> allSettings = new ArrayList<>(getDynamicSettings());
allSettings.addAll(getSecureSettings());
return allSettings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ protected List<XPackUsageFeatureAction> usageActions() {
}
}

final Monitoring monitoring;

public LocalStateMonitoring(final Settings settings, final Path configPath) throws Exception {
super(settings, configPath);
LocalStateMonitoring thisVar = this;

plugins.add(new Monitoring(settings) {
monitoring = new Monitoring(settings) {
@Override
protected SSLService getSslService() {
return thisVar.getSslService();
Expand All @@ -63,7 +65,8 @@ protected LicenseService getLicenseService() {
protected XPackLicenseState getLicenseState() {
return thisVar.getLicenseState();
}
});
};
plugins.add(monitoring);
plugins.add(new Watcher(settings) {
@Override
protected SSLService getSslService() {
Expand All @@ -78,6 +81,10 @@ protected XPackLicenseState getLicenseState() {
plugins.add(new IndexLifecycle(settings));
}

public Monitoring getMonitoring() {
return monitoring;
}

@Override
protected Class<? extends TransportAction<XPackUsageRequest, XPackUsageResponse>> getUsageAction() {
return MonitoringTransportXPackUsageAction.class;
Expand Down
Loading