diff --git a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java index 6963cd2fab14c..e9df728c7780a 100644 --- a/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java +++ b/x-pack/plugin/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporter.java @@ -177,7 +177,47 @@ public Iterator> settings() { */ public static final Setting.AffixSetting AUTH_USERNAME_SETTING = Setting.affixKeySetting("xpack.monitoring.exporters.","auth.username", - (key) -> Setting.simpleString(key, Property.Dynamic, Property.NodeScope, Property.Filtered)); + (key) -> Setting.simpleString( + key, + new Setting.Validator() { + @Override + public void validate(String password) { + // no username validation that is independent of other settings + } + + @Override + public void validate(String username, Map, Object> settings) { + 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"); + } + } + } + + @Override + public Iterator> settings() { + final String namespace = + HttpExporter.AUTH_USERNAME_SETTING.getNamespace( + HttpExporter.AUTH_USERNAME_SETTING.getConcreteSetting(key)); + final List> settings = List.of( + HttpExporter.AUTH_PASSWORD_SETTING.getConcreteSettingForNamespace(namespace)); + return settings.iterator(); + } + + }, + Property.Dynamic, + Property.NodeScope, + Property.Filtered)); /** * Password for basic auth. */ diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java index 79a5da518e7c1..c8f4fb6958aa7 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/exporter/http/HttpExporterTests.java @@ -238,6 +238,28 @@ public void testExporterWithPasswordButNoUsername() { assertThat(exception.getMessage(), equalTo(expected)); } + public void testExporterWithUsernameButNoPassword() { + final String expected = + "[xpack.monitoring.exporters._http.auth.username] is set but [xpack.monitoring.exporters._http.auth.password] is missing"; + final String prefix = "xpack.monitoring.exporters._http"; + final Settings settings = Settings.builder() + .put(prefix + ".type", HttpExporter.TYPE) + .put(prefix + ".host", "localhost:9200") + .put(prefix + ".auth.username", "_user") + .build(); + + final IllegalArgumentException e = expectThrows( + IllegalArgumentException.class, + () -> HttpExporter.AUTH_USERNAME_SETTING.getConcreteSetting(prefix + ".auth.username").get(settings)); + assertThat( + e, + hasToString( + containsString("Failed to parse value for setting [xpack.monitoring.exporters._http.auth.username]"))); + + assertThat(e.getCause(), instanceOf(SettingsException.class)); + assertThat(e.getCause(), hasToString(containsString(expected))); + } + public void testExporterWithUnknownBlacklistedClusterAlerts() { final SSLIOSessionStrategy sslStrategy = mock(SSLIOSessionStrategy.class); when(sslService.sslIOSessionStrategy(any(Settings.class))).thenReturn(sslStrategy);