Skip to content

Commit 4083eae

Browse files
authored
[7.x] Secure password for monitoring HTTP exporter (#51775)
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.
1 parent 1545c2a commit 4083eae

File tree

10 files changed

+210
-52
lines changed

10 files changed

+210
-52
lines changed

docs/reference/settings/monitoring-settings.asciidoc

+12-6
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ For more information, see <<monitor-elasticsearch-cluster>>.
2929
==== General Monitoring Settings
3030

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

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

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

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

199199
`auth.username`::
200200

201-
The username is required if a `auth.password` is supplied.
201+
The username is required if `auth.secure_password` or `auth.password` is supplied.
202+
203+
`auth.secure_password` (<<secure-settings,Secure>>, <<reloadable-secure-settings,reloadable>>)::
204+
205+
The password for the `auth.username`. Takes precedence over `auth.password` if it is also specified.
202206

203207
`auth.password`::
204208

205-
The password for the `auth.username`.
209+
The password for the `auth.username`. If `auth.secure_password` is also specified, this setting is ignored.
210+
211+
deprecated[7.7.0, Use `auth.secure_password` instead.]
206212

207213
`connection.timeout`::
208214

docs/reference/setup/secure-settings.asciidoc

+1
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ There are reloadable secure settings for:
5959
* {plugins}/discovery-ec2-usage.html#_configuring_ec2_discovery[The EC2 discovery plugin]
6060
* {plugins}/repository-gcs-client.html[The GCS repository plugin]
6161
* {plugins}/repository-s3-client.html[The S3 repository plugin]
62+
* <<monitoring-settings>>

x-pack/plugin/core/src/test/java/org/elasticsearch/test/http/MockWebServer.java

+7
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ public MockRequest takeRequest() {
244244
return requests.poll();
245245
}
246246

247+
/**
248+
* Removes all requests from the queue.
249+
*/
250+
public void clearRequests() {
251+
requests.clear();
252+
}
253+
247254
/**
248255
* A utility method to peek into the requests and find out if #MockWebServer.takeRequests will not throw an out of bound exception
249256
* @return true if more requests are available, false otherwise

x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/Monitoring.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.license.XPackLicenseState;
2727
import org.elasticsearch.plugins.ActionPlugin;
2828
import org.elasticsearch.plugins.Plugin;
29+
import org.elasticsearch.plugins.ReloadablePlugin;
2930
import org.elasticsearch.rest.RestController;
3031
import org.elasticsearch.rest.RestHandler;
3132
import org.elasticsearch.script.ScriptService;
@@ -73,7 +74,7 @@
7374
* - node clients: all modules are bound
7475
* - transport clients: only action/transport actions are bound
7576
*/
76-
public class Monitoring extends Plugin implements ActionPlugin {
77+
public class Monitoring extends Plugin implements ActionPlugin, ReloadablePlugin {
7778

7879
/**
7980
* The ability to automatically cleanup ".watcher_history*" indices while also cleaning up Monitoring indices.
@@ -86,6 +87,8 @@ public class Monitoring extends Plugin implements ActionPlugin {
8687
private final boolean enabled;
8788
private final boolean transportClientMode;
8889

90+
private Exporters exporters;
91+
8992
public Monitoring(Settings settings) {
9093
this.settings = settings;
9194
this.transportClientMode = XPackPlugin.transportClientMode(settings);
@@ -134,8 +137,7 @@ public Collection<Object> createComponents(Client client, ClusterService cluster
134137
Map<String, Exporter.Factory> exporterFactories = new HashMap<>();
135138
exporterFactories.put(HttpExporter.TYPE, config -> new HttpExporter(config, dynamicSSLService, threadPool.getThreadContext()));
136139
exporterFactories.put(LocalExporter.TYPE, config -> new LocalExporter(config, client, cleanerService));
137-
final Exporters exporters = new Exporters(settings, exporterFactories, clusterService, getLicenseState(),
138-
threadPool.getThreadContext());
140+
exporters = new Exporters(settings, exporterFactories, clusterService, getLicenseState(), threadPool.getThreadContext());
139141

140142
Set<Collector> collectors = new HashSet<>();
141143
collectors.add(new IndexStatsCollector(clusterService, getLicenseState(), client));
@@ -196,4 +198,13 @@ public List<String> getSettingsFilter() {
196198
final String exportersKey = "xpack.monitoring.exporters.";
197199
return Collections.unmodifiableList(Arrays.asList(exportersKey + "*.auth.*", exportersKey + "*.ssl.*"));
198200
}
201+
202+
@Override
203+
public void reload(Settings settings) throws Exception {
204+
final List<String> changedExporters = HttpExporter.loadSettings(settings);
205+
for (String changedExporter : changedExporters) {
206+
final Settings settingsForChangedExporter = settings.filter(x -> x.startsWith("xpack.monitoring.exporters." + changedExporter));
207+
exporters.setExportersSetting(settingsForChangedExporter);
208+
}
209+
}
199210
}

x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringService.java

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.xpack.core.monitoring.exporter.MonitoringDoc;
2323
import org.elasticsearch.xpack.monitoring.collector.Collector;
2424
import org.elasticsearch.xpack.monitoring.exporter.Exporters;
25+
import org.elasticsearch.xpack.monitoring.exporter.http.HttpExporter;
2526

2627
import java.io.Closeable;
2728
import java.util.ArrayList;
@@ -104,6 +105,8 @@ public class MonitoringService extends AbstractLifecycleComponent {
104105
.addSettingsUpdateConsumer(ELASTICSEARCH_COLLECTION_ENABLED, this::setElasticsearchCollectionEnabled);
105106
clusterService.getClusterSettings().addSettingsUpdateConsumer(ENABLED, this::setMonitoringActive);
106107
clusterService.getClusterSettings().addSettingsUpdateConsumer(INTERVAL, this::setInterval);
108+
109+
HttpExporter.loadSettings(settings);
107110
}
108111

109112
void setElasticsearchCollectionEnabled(final boolean enabled) {

x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/Exporters.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.Objects;
3636
import java.util.Set;
3737
import java.util.concurrent.atomic.AtomicReference;
38+
import java.util.stream.Collectors;
3839

3940
import static java.util.Collections.emptyMap;
4041

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

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

69-
private void setExportersSetting(Settings exportersSetting) {
72+
public void setExportersSetting(Settings exportersSetting) {
7073
if (this.lifecycle.started()) {
7174
Map<String, Exporter> updated = initExporters(exportersSetting);
7275
closeExporters(logger, this.exporters.getAndSet(updated));

x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java

+65-13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.elasticsearch.common.Strings;
2727
import org.elasticsearch.common.bytes.BytesReference;
2828
import org.elasticsearch.common.collect.MapBuilder;
29+
import org.elasticsearch.common.settings.SecureSetting;
30+
import org.elasticsearch.common.settings.SecureString;
2931
import org.elasticsearch.common.settings.Setting;
3032
import org.elasticsearch.common.settings.Setting.Property;
3133
import org.elasticsearch.common.settings.Settings;
@@ -52,6 +54,7 @@
5254
import java.util.Map;
5355
import java.util.Objects;
5456
import java.util.Set;
57+
import java.util.concurrent.ConcurrentHashMap;
5558
import java.util.concurrent.atomic.AtomicBoolean;
5659
import java.util.function.Function;
5760
import java.util.function.Supplier;
@@ -205,23 +208,20 @@ public void validate(final String username, final Map<Setting<?>, Object> settin
205208
final String namespace =
206209
HttpExporter.AUTH_USERNAME_SETTING.getNamespace(
207210
HttpExporter.AUTH_USERNAME_SETTING.getConcreteSetting(key));
208-
final String password =
209-
(String) settings.get(AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace));
210211

211212
// password must be specified along with username for any auth
212213
if (Strings.isNullOrEmpty(username) == false) {
213-
if (Strings.isNullOrEmpty(password)) {
214-
throw new SettingsException(
215-
"[" + AUTH_USERNAME_SETTING.getConcreteSettingForNamespace(namespace).getKey() + "] is set " +
216-
"but [" + AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace).getKey() + "] is " +
217-
"missing");
218-
}
219214
final String type =
220215
(String) settings.get(Exporter.TYPE_SETTING.getConcreteSettingForNamespace(namespace));
221216
if ("http".equals(type) == false) {
222217
throw new SettingsException("username for [" + key + "] is set but type is [" + type + "]");
223218
}
224219
}
220+
221+
// it would be ideal to validate that just one of either AUTH_PASSWORD_SETTING or
222+
// AUTH_SECURE_PASSWORD_SETTING were present here, but that is not currently possible with the settings
223+
// validation framework.
224+
// https://github.com/elastic/elasticsearch/issues/51332
225225
}
226226

227227
@Override
@@ -231,8 +231,8 @@ public Iterator<Setting<?>> settings() {
231231
HttpExporter.AUTH_USERNAME_SETTING.getConcreteSetting(key));
232232

233233
final List<Setting<?>> settings = Arrays.asList(
234-
Exporter.TYPE_SETTING.getConcreteSettingForNamespace(namespace),
235-
HttpExporter.AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace));
234+
Exporter.TYPE_SETTING.getConcreteSettingForNamespace(namespace));
235+
236236
return settings.iterator();
237237
}
238238

@@ -284,8 +284,18 @@ public Iterator<Setting<?>> settings() {
284284
},
285285
Property.Dynamic,
286286
Property.NodeScope,
287-
Property.Filtered),
287+
Property.Filtered,
288+
Property.Deprecated),
288289
TYPE_DEPENDENCY);
290+
/**
291+
* Secure password for basic auth.
292+
*/
293+
public static final Setting.AffixSetting<SecureString> AUTH_SECURE_PASSWORD_SETTING =
294+
Setting.affixKeySetting(
295+
"xpack.monitoring.exporters.",
296+
"auth.secure_password",
297+
key -> SecureSetting.secureString(key, null),
298+
TYPE_DEPENDENCY);
289299
/**
290300
* The SSL settings.
291301
*
@@ -400,6 +410,7 @@ public Iterator<Setting<?>> settings() {
400410
*/
401411
private final AtomicBoolean clusterAlertsAllowed = new AtomicBoolean(false);
402412

413+
private static final ConcurrentHashMap<String, SecureString> SECURE_AUTH_PASSWORDS = new ConcurrentHashMap<>();
403414
private final ThreadContext threadContext;
404415
private final DateFormatter dateTimeFormatter;
405416

@@ -697,6 +708,25 @@ private static void configureTimeouts(final RestClientBuilder builder, final Con
697708
builder.setRequestConfigCallback(new TimeoutRequestConfigCallback(connectTimeout, socketTimeout));
698709
}
699710

711+
712+
/**
713+
* Caches secure settings for use when dynamically configuring HTTP exporters
714+
* @param settings settings used for configuring HTTP exporter
715+
* @return names of HTTP exporters whose secure settings changed, if any
716+
*/
717+
public static List<String> loadSettings(Settings settings) {
718+
final List<String> changedExporters = new ArrayList<>();
719+
for (final String namespace : AUTH_SECURE_PASSWORD_SETTING.getNamespaces(settings)) {
720+
final Setting<SecureString> s = AUTH_SECURE_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace);
721+
final SecureString securePassword = s.get(settings);
722+
final SecureString existingPassword = SECURE_AUTH_PASSWORDS.put(namespace, securePassword);
723+
if (securePassword.equals(existingPassword) == false) {
724+
changedExporters.add(namespace);
725+
}
726+
}
727+
return changedExporters;
728+
}
729+
700730
/**
701731
* Creates the optional {@link CredentialsProvider} with the username/password to use with <em>all</em> requests for user
702732
* authentication.
@@ -708,7 +738,19 @@ private static void configureTimeouts(final RestClientBuilder builder, final Con
708738
@Nullable
709739
private static CredentialsProvider createCredentialsProvider(final Config config) {
710740
final String username = AUTH_USERNAME_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings());
711-
final String password = AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings());
741+
742+
final String deprecatedPassword = AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(config.name()).get(config.settings());
743+
final SecureString securePassword = SECURE_AUTH_PASSWORDS.get(config.name());
744+
final String password;
745+
if (securePassword != null) {
746+
password = securePassword.toString();
747+
if (Strings.isNullOrEmpty(deprecatedPassword) == false) {
748+
logger.warn("exporter [{}] specified both auth.secure_password and auth.password. using auth.secure_password and " +
749+
"ignoring auth.password", config.name());
750+
}
751+
} else {
752+
password = deprecatedPassword;
753+
}
712754

713755
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
714756
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
@@ -873,9 +915,19 @@ public void doClose() {
873915
}
874916
}
875917

876-
public static List<Setting.AffixSetting<?>> getSettings() {
918+
public static List<Setting.AffixSetting<?>> getDynamicSettings() {
877919
return Arrays.asList(HOST_SETTING, TEMPLATE_CREATE_LEGACY_VERSIONS_SETTING, AUTH_PASSWORD_SETTING, AUTH_USERNAME_SETTING,
878920
BULK_TIMEOUT_SETTING, CONNECTION_READ_TIMEOUT_SETTING, CONNECTION_TIMEOUT_SETTING, PIPELINE_CHECK_TIMEOUT_SETTING,
879921
PROXY_BASE_PATH_SETTING, SNIFF_ENABLED_SETTING, TEMPLATE_CHECK_TIMEOUT_SETTING, SSL_SETTING, HEADERS_SETTING);
880922
}
923+
924+
public static List<Setting.AffixSetting<?>> getSecureSettings() {
925+
return Collections.singletonList(AUTH_SECURE_PASSWORD_SETTING);
926+
}
927+
928+
public static List<Setting.AffixSetting<?>> getSettings() {
929+
List<Setting.AffixSetting<?>> allSettings = new ArrayList<>(getDynamicSettings());
930+
allSettings.addAll(getSecureSettings());
931+
return allSettings;
932+
}
881933
}

x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/LocalStateMonitoring.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717

1818
public class LocalStateMonitoring extends LocalStateCompositeXPackPlugin {
1919

20+
final Monitoring monitoring;
21+
2022
public LocalStateMonitoring(final Settings settings, final Path configPath) throws Exception {
2123
super(settings, configPath);
2224
LocalStateMonitoring thisVar = this;
2325

24-
plugins.add(new Monitoring(settings) {
26+
monitoring = new Monitoring(settings) {
2527
@Override
2628
protected SSLService getSslService() {
2729
return thisVar.getSslService();
@@ -36,7 +38,8 @@ protected LicenseService getLicenseService() {
3638
protected XPackLicenseState getLicenseState() {
3739
return thisVar.getLicenseState();
3840
}
39-
});
41+
};
42+
plugins.add(monitoring);
4043
plugins.add(new Watcher(settings) {
4144
@Override
4245
protected SSLService getSslService() {
@@ -50,4 +53,9 @@ protected XPackLicenseState getLicenseState() {
5053
});
5154
plugins.add(new IndexLifecycle(settings));
5255
}
56+
57+
public Monitoring getMonitoring() {
58+
return monitoring;
59+
}
60+
5361
}

0 commit comments

Comments
 (0)