diff --git a/elasticsearch/build.gradle b/elasticsearch/build.gradle index c0d83f4e83e..b83ecb35ba4 100644 --- a/elasticsearch/build.gradle +++ b/elasticsearch/build.gradle @@ -1,3 +1,5 @@ +import org.elasticsearch.gradle.precommit.LicenseHeadersTask + File checkstyleSuppressions = file('checkstyle_suppressions.xml') subprojects { tasks.withType(Checkstyle) { @@ -7,4 +9,9 @@ subprojects { suppressions: checkstyleSuppressions ] } + + tasks.withType(LicenseHeadersTask.class) { + approvedLicenses = ['Elasticsearch Confidential'] + additionalLicense 'ESCON', 'Elasticsearch Confidential', 'ELASTICSEARCH CONFIDENTIAL' + } } diff --git a/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/License.java b/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/License.java index 403b9870f3c..f07d38edeed 100644 --- a/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/License.java +++ b/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/License.java @@ -101,6 +101,7 @@ public class License implements ToXContent { case "gold": return GOLD; case "platinum": + case "cloud_internal": case "internal": // bwc for 1.x subscription_type field return PLATINUM; default: @@ -196,12 +197,42 @@ public class License implements ToXContent { } /** - * @return the operation mode of the license as computed from the license type + * @return the operation mode of the license as computed from the license type or from + * the license mode file */ public OperationMode operationMode() { + synchronized (this) { + if (canReadOperationModeFromFile() && operationModeFileWatcher != null) { + return operationModeFileWatcher.getCurrentOperationMode(); + } + } return operationMode; } + private boolean canReadOperationModeFromFile() { + return type.equals("cloud_internal"); + } + + private volatile OperationModeFileWatcher operationModeFileWatcher; + + /** + * Sets the operation mode file watcher for the license and initializes the + * file watcher when the license type allows to override operation mode from file + */ + public synchronized void setOperationModeFileWatcher(final OperationModeFileWatcher operationModeFileWatcher) { + this.operationModeFileWatcher = operationModeFileWatcher; + if (canReadOperationModeFromFile()) { + this.operationModeFileWatcher.init(); + } + } + + /** + * Removes operation mode file watcher, so unused license objects can be gc'ed + */ + public synchronized void removeOperationModeFileWatcher() { + this.operationModeFileWatcher = null; + } + /** * @return the current license's status */ diff --git a/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/OperationModeFileWatcher.java b/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/OperationModeFileWatcher.java new file mode 100644 index 00000000000..3ba63e3cdd3 --- /dev/null +++ b/elasticsearch/license/base/src/main/java/org/elasticsearch/license/core/OperationModeFileWatcher.java @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license.core; + + +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.license.core.License.OperationMode; +import org.elasticsearch.watcher.FileChangesListener; +import org.elasticsearch.watcher.FileWatcher; +import org.elasticsearch.watcher.ResourceWatcherService; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * File based watcher for license {@link OperationMode} + * Watches for changes in licenseModePath, use + * {@link #getCurrentOperationMode()} to access the latest mode + * + * In case of failure to read a valid operation mode from licenseModePath, + * the operation mode will default to PLATINUM + */ +public final class OperationModeFileWatcher extends FileChangesListener { + private final ResourceWatcherService resourceWatcherService; + private final Path licenseModePath; + private final AtomicBoolean initialized = new AtomicBoolean(); + private final OperationMode defaultOperationMode = OperationMode.PLATINUM; + private volatile OperationMode currentOperationMode = defaultOperationMode; + private final ESLogger logger; + private final Runnable onChange; + + public OperationModeFileWatcher(ResourceWatcherService resourceWatcherService, Path licenseModePath, + ESLogger logger, Runnable onChange) { + this.resourceWatcherService = resourceWatcherService; + this.licenseModePath = licenseModePath; + this.logger = logger; + this.onChange = onChange; + } + + public void init() { + if (initialized.compareAndSet(false, true)) { + final FileWatcher watcher = new FileWatcher(licenseModePath); + watcher.addListener(this); + try { + resourceWatcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); + if (Files.exists(licenseModePath)) { + onChange(licenseModePath); + } + } catch (IOException e) { + logger.error("couldn't initialize watching license mode file", e); + } + } + } + + /** + * Returns the current operation mode based on license mode file. + * Defaults to {@link OperationMode#PLATINUM} + */ + public OperationMode getCurrentOperationMode() { + return currentOperationMode; + } + + @Override + public void onFileInit(Path file) { + onChange(file); + } + + @Override + public void onFileCreated(Path file) { + onChange(file); + } + + @Override + public void onFileDeleted(Path file) { + onChange(file); + } + + @Override + public void onFileChanged(Path file) { + onChange(file); + } + + private synchronized void onChange(Path file) { + if (file.equals(licenseModePath)) { + currentOperationMode = defaultOperationMode; + if (Files.exists(licenseModePath) + && Files.isReadable(licenseModePath)) { + final byte[] content; + try { + content = Files.readAllBytes(licenseModePath); + } catch (IOException e) { + logger.error("couldn't read operation mode from [{}]", e, licenseModePath.toAbsolutePath().toString()); + return; + } + String operationMode = new String(content, StandardCharsets.UTF_8); + try { + currentOperationMode = OperationMode.resolve(operationMode); + } catch (IllegalArgumentException e) { + logger.error("invalid operation mode in [{}]", e, licenseModePath.toAbsolutePath().toString()); + return; + } + } + onChange.run(); + } + } +} + diff --git a/elasticsearch/license/base/src/test/java/org/elasticsearch/license/core/LicenseOperationModeUpdateTests.java b/elasticsearch/license/base/src/test/java/org/elasticsearch/license/core/LicenseOperationModeUpdateTests.java new file mode 100644 index 00000000000..8bd379862cc --- /dev/null +++ b/elasticsearch/license/base/src/test/java/org/elasticsearch/license/core/LicenseOperationModeUpdateTests.java @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license.core; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.watcher.FileWatcher; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.junit.Before; + +import java.nio.file.Path; + +import static org.elasticsearch.license.core.OperationModeFileWatcherTests.writeMode; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class LicenseOperationModeUpdateTests extends ESTestCase { + + private OperationModeFileWatcher operationModeFileWatcher; + private Path licenseModeFile; + private ResourceWatcherService resourceWatcherService; + + @Before + public void init() throws Exception { + licenseModeFile = createTempFile(); + resourceWatcherService = mock(ResourceWatcherService.class); + operationModeFileWatcher = new OperationModeFileWatcher(resourceWatcherService, licenseModeFile, logger, () -> {}); + } + + public void testLicenseOperationModeUpdate() throws Exception { + String type = randomFrom("trial", "basic", "standard", "gold", "platinum"); + License license = License.builder() + .uid("id") + .expiryDate(0) + .issueDate(0) + .issuedTo("elasticsearch") + .issuer("issuer") + .type(type) + .maxNodes(1) + .build(); + + assertThat(license.operationMode(), equalTo(License.OperationMode.resolve(type))); + writeMode("gold", licenseModeFile); + license.setOperationModeFileWatcher(operationModeFileWatcher); + verifyZeroInteractions(resourceWatcherService); + assertThat(license.operationMode(), equalTo(License.OperationMode.resolve(type))); + } + + public void testCloudInternalLicenseOperationModeUpdate() throws Exception { + License license = License.builder() + .uid("id") + .expiryDate(0) + .issueDate(0) + .issuedTo("elasticsearch") + .issuer("issuer") + .type("cloud_internal") + .maxNodes(1) + .build(); + + assertThat(license.operationMode(), equalTo(License.OperationMode.PLATINUM)); + writeMode("gold", licenseModeFile); + license.setOperationModeFileWatcher(operationModeFileWatcher); + verify(resourceWatcherService, times(1)).add(any(FileWatcher.class), eq(ResourceWatcherService.Frequency.HIGH)); + assertThat(license.operationMode(), equalTo(License.OperationMode.GOLD)); + } +} diff --git a/elasticsearch/license/base/src/test/java/org/elasticsearch/license/core/OperationModeFileWatcherTests.java b/elasticsearch/license/base/src/test/java/org/elasticsearch/license/core/OperationModeFileWatcherTests.java new file mode 100644 index 00000000000..a51ea032c91 --- /dev/null +++ b/elasticsearch/license/base/src/test/java/org/elasticsearch/license/core/OperationModeFileWatcherTests.java @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license.core; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.hamcrest.Matchers.equalTo; + +public class OperationModeFileWatcherTests extends ESTestCase { + private ResourceWatcherService watcherService; + private TestThreadPool threadPool; + private Path licenseModePath; + private OperationModeFileWatcher operationModeFileWatcher; + private AtomicInteger onChangeCounter; + + @Before + public void setup() throws Exception { + threadPool = new TestThreadPool("license mode file watcher tests"); + Settings settings = Settings.builder() + .put("resource.reload.interval.high", "10ms") + .build(); + watcherService = new ResourceWatcherService(settings, + threadPool); + watcherService.start(); + licenseModePath = createTempFile(); + onChangeCounter = new AtomicInteger(); + operationModeFileWatcher = new OperationModeFileWatcher(watcherService, licenseModePath, logger, + () -> onChangeCounter.incrementAndGet()); + } + + @After + public void shutdown() throws InterruptedException { + terminate(threadPool); + watcherService.stop(); + } + + public void testInit() throws Exception { + writeMode("gold"); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + operationModeFileWatcher.init(); + assertThat(onChangeCounter.get(), equalTo(2)); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD)); + } + + public void testUpdateModeFromFile() throws Exception { + Files.delete(licenseModePath); + operationModeFileWatcher.init(); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + writeMode("gold"); + assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD))); + writeMode("basic"); + assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.BASIC))); + assertThat(onChangeCounter.get(), equalTo(2)); + } + + public void testDeleteModeFromFile() throws Exception { + Files.delete(licenseModePath); + operationModeFileWatcher.init(); + writeMode("gold"); + assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD))); + Files.delete(licenseModePath); + assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM))); + assertThat(onChangeCounter.get(), equalTo(2)); + } + + public void testInvalidModeFromFile() throws Exception { + writeMode("invalid"); + operationModeFileWatcher.init(); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + operationModeFileWatcher.onFileChanged(licenseModePath); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + } + + public void testLicenseModeFileIsDirectory() throws Exception { + licenseModePath = createTempDir(); + operationModeFileWatcher.init(); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + operationModeFileWatcher.onFileChanged(licenseModePath); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + } + + public void testLicenseModeFileCreatedAfterInit() throws Exception { + Files.delete(licenseModePath); + operationModeFileWatcher.init(); + assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.PLATINUM)); + Path tempFile = createTempFile(); + writeMode("gold", tempFile); + licenseModePath = tempFile; + assertBusy(() -> assertThat(operationModeFileWatcher.getCurrentOperationMode(), equalTo(License.OperationMode.GOLD))); + } + + private void writeMode(String mode) throws IOException { + writeMode(mode, licenseModePath); + } + + static void writeMode(String mode, Path file) throws IOException { + Files.write(file, mode.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); + } +} diff --git a/elasticsearch/license/build.gradle b/elasticsearch/license/build.gradle index f4f03e52e7a..b4e221e7884 100644 --- a/elasticsearch/license/build.gradle +++ b/elasticsearch/license/build.gradle @@ -3,7 +3,5 @@ subprojects { project.forbiddenPatterns { exclude '**/*.key' } - // someone figure out what the x-plugins logic should be - project.licenseHeaders.enabled = false } } diff --git a/elasticsearch/qa/audit-tests/build.gradle b/elasticsearch/qa/audit-tests/build.gradle index 319824d30bb..ac432795388 100644 --- a/elasticsearch/qa/audit-tests/build.gradle +++ b/elasticsearch/qa/audit-tests/build.gradle @@ -14,7 +14,7 @@ project.sourceSets.test.output.dir(outputDir, builtBy: copyXPackPluginProps) integTest { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'xpack.security.audit.enabled', 'true' setting 'xpack.security.audit.outputs', 'index' setting 'logger.level', 'DEBUG' diff --git a/elasticsearch/qa/audit-tests/src/test/java/org/elasticsearch/xpack/security/audit/IndexAuditIT.java b/elasticsearch/qa/audit-tests/src/test/java/org/elasticsearch/xpack/security/audit/IndexAuditIT.java index b869a08d2b7..a54102360ec 100644 --- a/elasticsearch/qa/audit-tests/src/test/java/org/elasticsearch/xpack/security/audit/IndexAuditIT.java +++ b/elasticsearch/qa/audit-tests/src/test/java/org/elasticsearch/xpack/security/audit/IndexAuditIT.java @@ -38,11 +38,10 @@ public class IndexAuditIT extends ESIntegTestCase { @AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2354") public void testShieldIndexAuditTrailWorking() throws Exception { - try (Response response = getRestClient().performRequest("GET", "/", + Response response = getRestClient().performRequest("GET", "/", new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, - UsernamePasswordToken.basicAuthHeaderValue(USER, new SecuredString(PASS.toCharArray()))))) { - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } + UsernamePasswordToken.basicAuthHeaderValue(USER, new SecuredString(PASS.toCharArray())))); + assertThat(response.getStatusLine().getStatusCode(), is(200)); final AtomicReference lastClusterState = new AtomicReference<>(); final AtomicBoolean indexExists = new AtomicBoolean(false); boolean found = awaitBusy(() -> { diff --git a/elasticsearch/qa/build.gradle b/elasticsearch/qa/build.gradle index 3c9541e6178..e69de29bb2d 100644 --- a/elasticsearch/qa/build.gradle +++ b/elasticsearch/qa/build.gradle @@ -1,6 +0,0 @@ -subprojects { - tasks.withType(org.elasticsearch.gradle.precommit.LicenseHeadersTask) { - // someone figure out what the x-plugins logic should be - project.licenseHeaders.enabled = false - } -} diff --git a/elasticsearch/qa/core-rest-tests-with-security/build.gradle b/elasticsearch/qa/core-rest-tests-with-security/build.gradle index 8f02ffb2c3a..acfc4029ec8 100644 --- a/elasticsearch/qa/core-rest-tests-with-security/build.gradle +++ b/elasticsearch/qa/core-rest-tests-with-security/build.gradle @@ -34,7 +34,7 @@ integTest { 'search/80_date_math_index_names/Missing index with catch'].join(',') cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'xpack.watcher.enabled', 'false' setting 'xpack.monitoring.enabled', 'false' setupCommand 'setupDummyUser', diff --git a/elasticsearch/qa/core-rest-tests-with-security/src/test/java/org/elasticsearch/xpack/security/RestIT.java b/elasticsearch/qa/core-rest-tests-with-security/src/test/java/org/elasticsearch/xpack/security/CoreWithSecurityClientYamlTestSuiteIT.java similarity index 71% rename from elasticsearch/qa/core-rest-tests-with-security/src/test/java/org/elasticsearch/xpack/security/RestIT.java rename to elasticsearch/qa/core-rest-tests-with-security/src/test/java/org/elasticsearch/xpack/security/CoreWithSecurityClientYamlTestSuiteIT.java index 184b21989d9..157e20b15ca 100644 --- a/elasticsearch/qa/core-rest-tests-with-security/src/test/java/org/elasticsearch/xpack/security/RestIT.java +++ b/elasticsearch/qa/core-rest-tests-with-security/src/test/java/org/elasticsearch/xpack/security/CoreWithSecurityClientYamlTestSuiteIT.java @@ -7,29 +7,30 @@ package org.elasticsearch.xpack.security; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class RestIT extends ESRestTestCase { +public class CoreWithSecurityClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String USER = "test_user"; private static final String PASS = "changeme"; - public RestIT(@Name("yaml") RestTestCandidate testCandidate) { + public CoreWithSecurityClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Override diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyManualExecutionIT.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyManualExecutionIT.java index 4796e9904fb..e39298590eb 100644 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyManualExecutionIT.java +++ b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyManualExecutionIT.java @@ -14,7 +14,7 @@ import org.elasticsearch.xpack.watcher.condition.script.ScriptCondition; import org.elasticsearch.xpack.watcher.execution.ManualExecutionContext; import org.elasticsearch.xpack.watcher.execution.ManualExecutionTests.ExecutionRunner; import org.elasticsearch.xpack.watcher.history.WatchRecord; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.transport.actions.delete.DeleteWatchResponse; import org.elasticsearch.xpack.watcher.transport.actions.get.GetWatchRequest; @@ -65,7 +65,7 @@ public class GroovyManualExecutionIT extends AbstractWatcherIntegrationTestCase WatchSourceBuilder watchBuilder = watchBuilder() .trigger(schedule(cron("0 0 0 1 * ? 2099"))) .input(simpleInput("foo", "bar")) - .condition(new ScriptCondition((new Script.Builder.Inline("sleep 100; return true")).build())) + .condition(new ScriptCondition((new WatcherScript.Builder.Inline("sleep 100; return true")).build())) .addAction("log", loggingAction("foobar")); Watch watch = watchParser().parse("_id", false, watchBuilder.buildAsBytes(XContentType.JSON)); @@ -80,7 +80,7 @@ public class GroovyManualExecutionIT extends AbstractWatcherIntegrationTestCase WatchSourceBuilder watchBuilder = watchBuilder() .trigger(schedule(cron("0 0 0 1 * ? 2099"))) .input(simpleInput("foo", "bar")) - .condition(new ScriptCondition((new Script.Builder.Inline("sleep 10000; return true")).build())) + .condition(new ScriptCondition((new WatcherScript.Builder.Inline("sleep 10000; return true")).build())) .defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS)) .addAction("log", loggingAction("foobar")); diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyScriptConditionIT.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyScriptConditionIT.java index 8f9b29ff95a..40050d8f114 100644 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyScriptConditionIT.java +++ b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/GroovyScriptConditionIT.java @@ -8,6 +8,7 @@ package org.elasticsearch.messy.tests; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.groovy.GroovyPlugin; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; @@ -16,8 +17,7 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.watcher.condition.script.ExecutableScriptCondition; import org.elasticsearch.xpack.watcher.condition.script.ScriptCondition; import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; -import org.elasticsearch.xpack.watcher.support.Script; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.watch.Payload; import org.junit.AfterClass; @@ -28,7 +28,7 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; -import static org.elasticsearch.messy.tests.MessyTestUtils.getScriptServiceProxy; +import static org.elasticsearch.messy.tests.MessyTestUtils.createScriptService; import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.mockExecutionContext; public class GroovyScriptConditionIT extends AbstractWatcherIntegrationTestCase { @@ -46,7 +46,7 @@ public class GroovyScriptConditionIT extends AbstractWatcherIntegrationTestCase } private static ThreadPool THREAD_POOL; - private ScriptServiceProxy scriptService; + private ScriptService scriptService; @BeforeClass public static void startThreadPool() { @@ -55,7 +55,7 @@ public class GroovyScriptConditionIT extends AbstractWatcherIntegrationTestCase @Before public void init() throws Exception { - scriptService = getScriptServiceProxy(THREAD_POOL); + scriptService = createScriptService(THREAD_POOL); } @AfterClass @@ -83,7 +83,7 @@ public class GroovyScriptConditionIT extends AbstractWatcherIntegrationTestCase SearchResponse unmetResponse = builder.get(); ExecutableScriptCondition condition = - new ExecutableScriptCondition(new ScriptCondition(Script.inline( + new ExecutableScriptCondition(new ScriptCondition(WatcherScript.inline( String.join( " ", "if (ctx.payload.hits.total < 1) return false;", diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/IndexActionIT.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/IndexActionIT.java deleted file mode 100644 index 6cb442b82ca..00000000000 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/IndexActionIT.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.messy.tests; - -import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.groovy.GroovyPlugin; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; -import org.elasticsearch.search.sort.SortOrder; -import org.elasticsearch.xpack.watcher.history.HistoryStore; -import org.elasticsearch.xpack.watcher.support.WatcherDateTimeUtils; -import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; -import org.elasticsearch.xpack.watcher.transport.actions.execute.ExecuteWatchResponse; -import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; -import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - -import java.util.List; - -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.index.query.QueryBuilders.matchQuery; -import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHistogram; -import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource; -import static org.elasticsearch.xpack.watcher.actions.ActionBuilders.indexAction; -import static org.elasticsearch.xpack.watcher.client.WatchSourceBuilders.watchBuilder; -import static org.elasticsearch.xpack.watcher.input.InputBuilders.searchInput; -import static org.elasticsearch.xpack.watcher.input.InputBuilders.simpleInput; -import static org.elasticsearch.xpack.watcher.transform.TransformBuilders.scriptTransform; -import static org.elasticsearch.xpack.watcher.trigger.TriggerBuilders.schedule; -import static org.elasticsearch.xpack.watcher.trigger.schedule.Schedules.cron; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.is; - -/** - * - */ -public class IndexActionIT extends AbstractWatcherIntegrationTestCase { - - @Override - protected List> pluginTypes() { - List> types = super.pluginTypes(); - types.add(GroovyPlugin.class); - return types; - } - - public void testSimple() throws Exception { - PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder() - .trigger(schedule(cron("0/1 * * * * ? 2020"))) - .input(simpleInput("foo", "bar")) - .addAction("index-buckets", indexAction("idx", "type").setExecutionTimeField("@timestamp"))) - .get(); - - assertThat(putWatchResponse.isCreated(), is(true)); - - DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC); - - ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id") - .setTriggerEvent(new ScheduleTriggerEvent(now, now)) - .get(); - - assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed")); - - flush("idx"); - refresh(); - - SearchResponse searchResponse = client().prepareSearch("idx").setTypes("type").get(); - assertThat(searchResponse.getHits().totalHits(), is(1L)); - SearchHit hit = searchResponse.getHits().getAt(0); - if (timeWarped()) { - assertThat(hit.getSource(), hasEntry("@timestamp", (Object) WatcherDateTimeUtils.formatDate(now))); - } else { - assertThat(hit.getSource(), hasKey("@timestamp")); - DateTime timestamp = WatcherDateTimeUtils.parseDate((String) hit.getSource().get("@timestamp")); - assertThat(timestamp.isEqual(now) || timestamp.isAfter(now), is(true)); - } - assertThat(hit.getSource(), hasEntry("foo", (Object) "bar")); - } - - public void testSimpleWithDocField() throws Exception { - PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder() - .trigger(schedule(cron("0/1 * * * * ? 2020"))) - .input(simpleInput("foo", "bar")) - .addAction("index-buckets", - scriptTransform("return [ '_doc' : ctx.payload ]"), - indexAction("idx", "type").setExecutionTimeField("@timestamp"))) - - .get(); - - assertThat(putWatchResponse.isCreated(), is(true)); - - DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC); - - ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id") - .setTriggerEvent(new ScheduleTriggerEvent(now, now)) - .get(); - - assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed")); - - flush("idx"); - refresh(); - - SearchResponse searchResponse = client().prepareSearch("idx").setTypes("type").get(); - assertThat(searchResponse.getHits().totalHits(), is(1L)); - SearchHit hit = searchResponse.getHits().getAt(0); - if (timeWarped()) { - assertThat(hit.getSource(), hasEntry("@timestamp", (Object) WatcherDateTimeUtils.formatDate(now))); - } else { - assertThat(hit.getSource(), hasKey("@timestamp")); - DateTime timestamp = WatcherDateTimeUtils.parseDate((String) hit.getSource().get("@timestamp")); - assertThat(timestamp.isEqual(now) || timestamp.isAfter(now), is(true)); - } - assertThat(hit.getSource(), hasEntry("foo", (Object) "bar")); - } - - public void testSimpleWithDocFieldWrongFieldType() throws Exception { - PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder() - .trigger(schedule(cron("0/1 * * * * ? 2020"))) - .input(simpleInput("foo", "bar")) - .addAction("index-buckets", - scriptTransform("return [ '_doc' : 1 ]"), - indexAction("idx", "type").setExecutionTimeField("@timestamp"))) - .get(); - - assertThat(putWatchResponse.isCreated(), is(true)); - - DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC); - - ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id") - .setTriggerEvent(new ScheduleTriggerEvent(now, now)) - .setRecordExecution(true) - .get(); - - assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed")); - - flush(); - refresh(); - - assertThat(client().admin().indices().prepareExists("idx").get().isExists(), is(false)); - - assertThat(docCount(HistoryStore.INDEX_PREFIX + "*", HistoryStore.DOC_TYPE, searchSource() - .query(matchQuery("result.actions.status", "failure"))), is(1L)); - - } - - public void testIndexAggsBucketsAsDocuments() throws Exception { - DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC); - long bucketCount = randomIntBetween(2, 5); - for (int i = 0; i < bucketCount; i++) { - index("idx", "type", jsonBuilder().startObject() - .field("timestamp", now.minusDays(i)) - .endObject()); - } - - flush("idx"); - refresh(); - - PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder() - .trigger(schedule(cron("0/1 * * * * ? 2020"))) - .input(searchInput(new SearchRequest("idx") - .types("type") - .searchType(SearchType.QUERY_THEN_FETCH) - .source(searchSource() - .aggregation(dateHistogram("trend") - .field("timestamp") - .dateHistogramInterval(DateHistogramInterval.DAY))))) - .addAction("index-buckets", - - // this transform takes the bucket list and assigns it to `_doc` - // this means each bucket will be indexed as a separate doc, - // so we expect to have the same number of documents as the number - // of buckets. - scriptTransform("return [ '_doc' : ctx.payload.aggregations.trend.buckets]"), - - indexAction("idx", "bucket").setExecutionTimeField("@timestamp"))) - - .get(); - - assertThat(putWatchResponse.isCreated(), is(true)); - - ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id") - .setTriggerEvent(new ScheduleTriggerEvent(now, now)) - .get(); - - assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed")); - - flush("idx"); - refresh(); - - SearchResponse searchResponse = client().prepareSearch("idx").setTypes("bucket") - .addSort("key", SortOrder.DESC) - .get(); - assertThat(searchResponse.getHits().getTotalHits(), is(bucketCount)); - DateTime key = now.withMillisOfDay(0); - int i = 0; - for (SearchHit hit : searchResponse.getHits()) { - if (timeWarped()) { - assertThat(hit.getSource(), hasEntry("@timestamp", (Object) WatcherDateTimeUtils.formatDate(now))); - } else { - assertThat(hit.getSource(), hasKey("@timestamp")); - DateTime timestamp = WatcherDateTimeUtils.parseDate((String) hit.getSource().get("@timestamp")); - assertThat(timestamp.isEqual(now) || timestamp.isAfter(now), is(true)); - } - assertThat(hit.getSource(), hasEntry("key", (Object) key.getMillis())); - key = key.minusDays(1); - } - } -} diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/MessyTestUtils.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/MessyTestUtils.java index f4584212067..7956c92b569 100644 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/MessyTestUtils.java +++ b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/MessyTestUtils.java @@ -6,9 +6,6 @@ package org.elasticsearch.messy.tests; import org.apache.lucene.util.LuceneTestCase; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -19,9 +16,8 @@ import org.elasticsearch.script.ScriptSettings; import org.elasticsearch.script.groovy.GroovyScriptEngineService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.junit.Ignore; -import org.mockito.Mockito; import java.util.Arrays; import java.util.Collections; @@ -29,7 +25,7 @@ import java.util.Collections; @Ignore // not a test. @SuppressForbidden(reason = "gradle is broken and tries to run me as a test") public final class MessyTestUtils { - public static ScriptServiceProxy getScriptServiceProxy(ThreadPool tp) throws Exception { + public static ScriptService createScriptService(ThreadPool tp) throws Exception { Settings settings = Settings.builder() .put("script.inline", "true") .put("script.indexed", "true") @@ -37,10 +33,10 @@ public final class MessyTestUtils { .build(); GroovyScriptEngineService groovyScriptEngineService = new GroovyScriptEngineService(settings); ScriptEngineRegistry scriptEngineRegistry = new ScriptEngineRegistry(Collections.singleton(groovyScriptEngineService)); - ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Arrays.asList(ScriptServiceProxy.INSTANCE)); + ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Arrays.asList(WatcherScript.CTX_PLUGIN)); ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry); - return ScriptServiceProxy.of(new ScriptService(settings, new Environment(settings), - new ResourceWatcherService(settings, tp), scriptEngineRegistry, scriptContextRegistry, scriptSettings)); + return new ScriptService(settings, new Environment(settings), new ResourceWatcherService(settings, tp), + scriptEngineRegistry, scriptContextRegistry, scriptSettings); } } diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionSearchIT.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionSearchIT.java index 18c8b508cd0..e5c09de7f5a 100644 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionSearchIT.java +++ b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionSearchIT.java @@ -10,6 +10,7 @@ import org.elasticsearch.action.search.ShardSearchFailure; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.Index; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.groovy.GroovyPlugin; import org.elasticsearch.search.SearchShardTarget; import org.elasticsearch.search.aggregations.AggregationBuilders; @@ -20,11 +21,10 @@ import org.elasticsearch.search.internal.InternalSearchHits; import org.elasticsearch.search.internal.InternalSearchResponse; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.watcher.condition.script.ExecutableScriptCondition; import org.elasticsearch.xpack.watcher.condition.script.ScriptCondition; import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.watch.Payload; import org.junit.After; @@ -40,7 +40,7 @@ import static org.mockito.Mockito.when; */ public class ScriptConditionSearchIT extends AbstractWatcherIntegrationTestCase { private ThreadPool tp = null; - private ScriptServiceProxy scriptService; + private ScriptService scriptService; @Override protected List> pluginTypes() { @@ -52,7 +52,7 @@ public class ScriptConditionSearchIT extends AbstractWatcherIntegrationTestCase @Before public void init() throws Exception { tp = new TestThreadPool(ThreadPool.Names.SAME); - scriptService = MessyTestUtils.getScriptServiceProxy(tp); + scriptService = MessyTestUtils.createScriptService(tp); } @After @@ -73,7 +73,7 @@ public class ScriptConditionSearchIT extends AbstractWatcherIntegrationTestCase .get(); ExecutableScriptCondition condition = new ExecutableScriptCondition( - new ScriptCondition(Script.inline("ctx.payload.aggregations.rate.buckets[0]?.doc_count >= 5").build()), + new ScriptCondition(WatcherScript.inline("ctx.payload.aggregations.rate.buckets[0]?.doc_count >= 5").build()), logger, scriptService); WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response)); @@ -92,7 +92,7 @@ public class ScriptConditionSearchIT extends AbstractWatcherIntegrationTestCase public void testExecuteAccessHits() throws Exception { ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition( - Script.inline("ctx.payload.hits?.hits[0]?._score == 1.0").build()), logger, scriptService); + WatcherScript.inline("ctx.payload.hits?.hits[0]?._score == 1.0").build()), logger, scriptService); InternalSearchHit hit = new InternalSearchHit(0, "1", new Text("type"), null); hit.score(1f); hit.shard(new SearchShardTarget("a", new Index("a", "testUUID"), 0)); diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionTests.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionTests.java index c42416b6cc3..e3e0ac4f069 100644 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionTests.java +++ b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/ScriptConditionTests.java @@ -15,17 +15,17 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.script.GeneralScriptException; import org.elasticsearch.script.ScriptException; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService.ScriptType; import org.elasticsearch.search.internal.InternalSearchResponse; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.watcher.condition.script.ExecutableScriptCondition; import org.elasticsearch.xpack.watcher.condition.script.ScriptCondition; import org.elasticsearch.xpack.watcher.condition.script.ScriptConditionFactory; import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.watch.Payload; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -36,7 +36,7 @@ import java.io.IOException; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.messy.tests.MessyTestUtils.getScriptServiceProxy; +import static org.elasticsearch.messy.tests.MessyTestUtils.createScriptService; import static org.elasticsearch.xpack.watcher.support.Exceptions.illegalArgument; import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.mockExecutionContext; import static org.hamcrest.Matchers.containsString; @@ -57,18 +57,18 @@ public class ScriptConditionTests extends ESTestCase { } public void testExecute() throws Exception { - ScriptServiceProxy scriptService = getScriptServiceProxy(tp); + ScriptService scriptService = createScriptService(tp); ExecutableScriptCondition condition = new ExecutableScriptCondition( - new ScriptCondition(Script.inline("ctx.payload.hits.total > 1").build()), logger, scriptService); + new ScriptCondition(WatcherScript.inline("ctx.payload.hits.total > 1").build()), logger, scriptService); SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500L, new ShardSearchFailure[0]); WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response)); assertFalse(condition.execute(ctx).met()); } public void testExecuteMergedParams() throws Exception { - ScriptServiceProxy scriptService = getScriptServiceProxy(tp); - Script script = Script.inline("ctx.payload.hits.total > threshold") - .lang(Script.DEFAULT_LANG).params(singletonMap("threshold", 1)).build(); + ScriptService scriptService = createScriptService(tp); + WatcherScript script = WatcherScript.inline("ctx.payload.hits.total > threshold") + .lang(WatcherScript.DEFAULT_LANG).params(singletonMap("threshold", 1)).build(); ExecutableScriptCondition executable = new ExecutableScriptCondition(new ScriptCondition(script), logger, scriptService); SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500L, new ShardSearchFailure[0]); WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response)); @@ -76,7 +76,7 @@ public class ScriptConditionTests extends ESTestCase { } public void testParserValid() throws Exception { - ScriptConditionFactory factory = new ScriptConditionFactory(Settings.builder().build(), getScriptServiceProxy(tp)); + ScriptConditionFactory factory = new ScriptConditionFactory(Settings.builder().build(), createScriptService(tp)); XContentBuilder builder = createConditionContent("ctx.payload.hits.total > 1", null, ScriptType.INLINE); @@ -103,7 +103,7 @@ public class ScriptConditionTests extends ESTestCase { } public void testParserInvalid() throws Exception { - ScriptConditionFactory factory = new ScriptConditionFactory(Settings.builder().build(), getScriptServiceProxy(tp)); + ScriptConditionFactory factory = new ScriptConditionFactory(Settings.builder().build(), createScriptService(tp)); XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject().endObject(); XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes()); @@ -118,7 +118,7 @@ public class ScriptConditionTests extends ESTestCase { } public void testScriptConditionParserBadScript() throws Exception { - ScriptConditionFactory conditionParser = new ScriptConditionFactory(Settings.builder().build(), getScriptServiceProxy(tp)); + ScriptConditionFactory conditionParser = new ScriptConditionFactory(Settings.builder().build(), createScriptService(tp)); ScriptType scriptType = randomFrom(ScriptType.values()); String script; switch (scriptType) { @@ -139,7 +139,7 @@ public class ScriptConditionTests extends ESTestCase { } public void testScriptConditionParser_badLang() throws Exception { - ScriptConditionFactory conditionParser = new ScriptConditionFactory(Settings.builder().build(), getScriptServiceProxy(tp)); + ScriptConditionFactory conditionParser = new ScriptConditionFactory(Settings.builder().build(), createScriptService(tp)); ScriptType scriptType = ScriptType.INLINE; String script = "return true"; XContentBuilder builder = createConditionContent(script, "not_a_valid_lang", scriptType); @@ -152,9 +152,9 @@ public class ScriptConditionTests extends ESTestCase { } public void testScriptConditionThrowException() throws Exception { - ScriptServiceProxy scriptService = getScriptServiceProxy(tp); + ScriptService scriptService = createScriptService(tp); ExecutableScriptCondition condition = new ExecutableScriptCondition( - new ScriptCondition(Script.inline("null.foo").build()), logger, scriptService); + new ScriptCondition(WatcherScript.inline("null.foo").build()), logger, scriptService); SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500L, new ShardSearchFailure[0]); WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response)); ScriptException exception = expectThrows(ScriptException.class, () -> condition.execute(ctx)); @@ -162,9 +162,9 @@ public class ScriptConditionTests extends ESTestCase { } public void testScriptConditionReturnObjectThrowsException() throws Exception { - ScriptServiceProxy scriptService = getScriptServiceProxy(tp); + ScriptService scriptService = createScriptService(tp); ExecutableScriptCondition condition = new ExecutableScriptCondition( - new ScriptCondition(Script.inline("return new Object()").build()), logger, scriptService); + new ScriptCondition(WatcherScript.inline("return new Object()").build()), logger, scriptService); SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500L, new ShardSearchFailure[0]); WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response)); Exception exception = expectThrows(GeneralScriptException.class, () -> condition.execute(ctx)); @@ -173,9 +173,9 @@ public class ScriptConditionTests extends ESTestCase { } public void testScriptConditionAccessCtx() throws Exception { - ScriptServiceProxy scriptService = getScriptServiceProxy(tp); + ScriptService scriptService = createScriptService(tp); ExecutableScriptCondition condition = new ExecutableScriptCondition( - new ScriptCondition(Script.inline("ctx.trigger.scheduled_time.getMillis() < new Date().time ").build()), + new ScriptCondition(WatcherScript.inline("ctx.trigger.scheduled_time.getMillis() < new Date().time ").build()), logger, scriptService); SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500L, new ShardSearchFailure[0]); WatchExecutionContext ctx = mockExecutionContext("_name", new DateTime(DateTimeZone.UTC), new Payload.XContent(response)); diff --git a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/TransformIT.java b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/TransformIT.java index 937b8c9e3e3..f9310ec2ef0 100644 --- a/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/TransformIT.java +++ b/elasticsearch/qa/messy-test-watcher-with-groovy/src/test/java/org/elasticsearch/messy/tests/TransformIT.java @@ -13,7 +13,7 @@ import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.groovy.GroovyPlugin; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; import org.elasticsearch.xpack.watcher.test.WatcherTestUtils; import org.elasticsearch.xpack.watcher.transport.actions.put.PutWatchResponse; @@ -72,10 +72,10 @@ public class TransformIT extends AbstractWatcherIntegrationTestCase { } public void testScriptTransform() throws Exception { - final Script script; + final WatcherScript script; if (randomBoolean()) { logger.info("testing script transform with an inline script"); - script = Script.inline("return [key3 : ctx.payload.key1 + ctx.payload.key2]").lang("groovy").build(); + script = WatcherScript.inline("return [key3 : ctx.payload.key1 + ctx.payload.key2]").lang("groovy").build(); } else if (randomBoolean()) { logger.info("testing script transform with an indexed script"); client().admin().cluster().preparePutStoredScript() @@ -83,10 +83,10 @@ public class TransformIT extends AbstractWatcherIntegrationTestCase { .setScriptLang("groovy") .setSource(new BytesArray("{\"script\" : \"return [key3 : ctx.payload.key1 + ctx.payload.key2]\"}")) .get(); - script = Script.indexed("_id").lang("groovy").build(); + script = WatcherScript.indexed("_id").lang("groovy").build(); } else { logger.info("testing script transform with a file script"); - script = Script.file("my-script").lang("groovy").build(); + script = WatcherScript.file("my-script").lang("groovy").build(); } // put a watch that has watch level transform: @@ -182,8 +182,8 @@ public class TransformIT extends AbstractWatcherIntegrationTestCase { } public void testChainTransform() throws Exception { - final Script script1 = Script.inline("return [key3 : ctx.payload.key1 + ctx.payload.key2]").lang("groovy").build(); - final Script script2 = Script.inline("return [key4 : ctx.payload.key3 + 10]").lang("groovy").build(); + final WatcherScript script1 = WatcherScript.inline("return [key3 : ctx.payload.key1 + ctx.payload.key2]").lang("groovy").build(); + final WatcherScript script2 = WatcherScript.inline("return [key4 : ctx.payload.key3 + 10]").lang("groovy").build(); // put a watch that has watch level transform: PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id1") .setSource(watchBuilder() diff --git a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchInputIT.java b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchInputIT.java index d0f15f2f26e..dcb2e95263e 100644 --- a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchInputIT.java +++ b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchInputIT.java @@ -28,7 +28,6 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.suggest.Suggesters; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.common.text.TextTemplate; import org.elasticsearch.xpack.watcher.actions.ActionWrapper; import org.elasticsearch.xpack.watcher.actions.ExecutableActions; @@ -41,7 +40,7 @@ import org.elasticsearch.xpack.watcher.input.search.SearchInput; import org.elasticsearch.xpack.watcher.input.search.SearchInputFactory; import org.elasticsearch.xpack.watcher.input.simple.ExecutableSimpleInput; import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateRequest; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateService; @@ -190,7 +189,7 @@ public class SearchInputIT extends ESIntegTestCase { Map params = new HashMap<>(); params.put("seconds_param", "30s"); - Script template = Script.inline(TEMPLATE_QUERY).lang("mustache").params(params).build(); + WatcherScript template = WatcherScript.inline(TEMPLATE_QUERY).lang("mustache").params(params).build(); SearchRequest request = client().prepareSearch() .setSearchType(ExecutableSearchInput.DEFAULT_SEARCH_TYPE) @@ -224,7 +223,7 @@ public class SearchInputIT extends ESIntegTestCase { Map params = new HashMap<>(); params.put("seconds_param", "30s"); - Script template = Script.indexed("test-template").lang("mustache").params(params).build(); + WatcherScript template = WatcherScript.indexed("test-template").lang("mustache").params(params).build(); jsonBuilder().value(TextTemplate.indexed("test-template").params(params).build()).bytes(); SearchRequest request = client().prepareSearch().setSearchType(ExecutableSearchInput.DEFAULT_SEARCH_TYPE) @@ -252,7 +251,7 @@ public class SearchInputIT extends ESIntegTestCase { Map params = new HashMap<>(); params.put("seconds_param", "30s"); - Script template = Script.file("test_disk_template").lang("mustache").params(params).build(); + WatcherScript template = WatcherScript.file("test_disk_template").lang("mustache").params(params).build(); SearchRequest request = client().prepareSearch().setSearchType(ExecutableSearchInput.DEFAULT_SEARCH_TYPE) .setIndices("test-search-index").request(); @@ -347,7 +346,8 @@ public class SearchInputIT extends ESIntegTestCase { timeValueSeconds(5)); } - private SearchInput.Result executeSearchInput(SearchRequest request, Script template, WatchExecutionContext ctx) throws IOException { + private SearchInput.Result executeSearchInput(SearchRequest request, WatcherScript template, + WatchExecutionContext ctx) throws IOException { createIndex("test-search-index"); ensureGreen("test-search-index"); SearchInput.Builder siBuilder = SearchInput.builder(new WatcherSearchTemplateRequest(request, template)); @@ -362,15 +362,15 @@ public class SearchInputIT extends ESIntegTestCase { protected WatcherSearchTemplateService watcherSearchTemplateService() { String master = internalCluster().getMasterName(); return new WatcherSearchTemplateService(internalCluster().clusterService(master).getSettings(), - ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class, master)), + internalCluster().getInstance(ScriptService.class, master), internalCluster().getInstance(IndicesQueriesRegistry.class, master), internalCluster().getInstance(AggregatorParsers.class, master), internalCluster().getInstance(Suggesters.class, master) ); } - protected ScriptServiceProxy scriptService() { - return ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)); + protected ScriptService scriptService() { + return internalCluster().getInstance(ScriptService.class); } private XContentSource toXContentSource(SearchInput.Result result) throws IOException { @@ -387,7 +387,7 @@ public class SearchInputIT extends ESIntegTestCase { @Override public ScriptContext.Plugin getCustomScriptContexts() { - return ScriptServiceProxy.INSTANCE; + return WatcherScript.CTX_PLUGIN; } } } diff --git a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchTransformIT.java b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchTransformIT.java index 2b469f17527..c45db63abb7 100644 --- a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchTransformIT.java +++ b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SearchTransformIT.java @@ -32,7 +32,6 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.suggest.Suggesters; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.common.text.TextTemplate; import org.elasticsearch.xpack.watcher.actions.ExecutableActions; import org.elasticsearch.xpack.watcher.condition.always.ExecutableAlwaysCondition; @@ -40,7 +39,7 @@ import org.elasticsearch.xpack.watcher.execution.TriggeredExecutionContext; import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; import org.elasticsearch.xpack.watcher.input.simple.ExecutableSimpleInput; import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateRequest; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateService; @@ -348,7 +347,7 @@ public class SearchTransformIT extends ESIntegTestCase { } if (templateName != null) { assertThat(executable.transform().getRequest().getTemplate(), - equalTo(Script.file("template1").build())); + equalTo(WatcherScript.file("template1").build())); } SearchSourceBuilder source = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()); assertThat(executable.transform().getRequest().getRequest().source(), equalTo(source)); @@ -381,7 +380,7 @@ public class SearchTransformIT extends ESIntegTestCase { Map params = new HashMap<>(); params.put("seconds_param", "30s"); - Script template = Script.inline(templateQuery).lang("mustache").params(params).build(); + WatcherScript template = WatcherScript.inline(templateQuery).lang("mustache").params(params).build(); SearchRequest request = client().prepareSearch().setSearchType(ExecutableSearchTransform.DEFAULT_SEARCH_TYPE) .setIndices("test-search-index").request(); @@ -415,7 +414,7 @@ public class SearchTransformIT extends ESIntegTestCase { Map params = new HashMap<>(); params.put("seconds_param", "30s"); - Script template = Script.indexed("test-script").lang("mustache").params(params).build(); + WatcherScript template = WatcherScript.indexed("test-script").lang("mustache").params(params).build(); SearchRequest request = client() .prepareSearch() @@ -441,7 +440,7 @@ public class SearchTransformIT extends ESIntegTestCase { Map params = new HashMap<>(); params.put("seconds_param", "30s"); - Script template = Script.file("test_disk_template").lang("mustache").params(params).build(); + WatcherScript template = WatcherScript.file("test_disk_template").lang("mustache").params(params).build(); SearchRequest request = client().prepareSearch().setSearchType(ExecutableSearchTransform.DEFAULT_SEARCH_TYPE) .setIndices("test-search-index").request(); @@ -504,7 +503,7 @@ public class SearchTransformIT extends ESIntegTestCase { timeValueSeconds(5)); } - private SearchTransform.Result executeSearchTransform(SearchRequest request, Script template, WatchExecutionContext ctx) + private SearchTransform.Result executeSearchTransform(SearchRequest request, WatcherScript template, WatchExecutionContext ctx) throws IOException { createIndex("test-search-index"); ensureGreen("test-search-index"); @@ -519,15 +518,15 @@ public class SearchTransformIT extends ESIntegTestCase { protected WatcherSearchTemplateService watcherSearchTemplateService() { String master = internalCluster().getMasterName(); return new WatcherSearchTemplateService(internalCluster().clusterService(master).getSettings(), - ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class, master)), + internalCluster().getInstance(ScriptService.class, master), internalCluster().getInstance(IndicesQueriesRegistry.class, master), internalCluster().getInstance(AggregatorParsers.class, master), internalCluster().getInstance(Suggesters.class, master) ); } - protected ScriptServiceProxy scriptService() { - return ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)); + protected ScriptService scriptService() { + return internalCluster().getInstance(ScriptService.class); } private static Map doc(String date, String value) { @@ -551,7 +550,7 @@ public class SearchTransformIT extends ESIntegTestCase { @Override public ScriptContext.Plugin getCustomScriptContexts() { - return ScriptServiceProxy.INSTANCE; + return WatcherScript.CTX_PLUGIN; } } } diff --git a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java index 99903025602..f84c4cc74ad 100644 --- a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java +++ b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java @@ -7,14 +7,13 @@ package org.elasticsearch.messy.tests; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; -import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.indices.TermsLookup; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.Template; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.mustache.MustachePlugin; -import org.elasticsearch.script.mustache.MustacheScriptEngineService; +import org.elasticsearch.script.mustache.TemplateQueryBuilder; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.security.authc.support.SecuredString; @@ -25,7 +24,6 @@ import java.util.ArrayList; import java.util.Collection; import static java.util.Collections.singletonMap; -import static org.elasticsearch.script.ScriptService.ScriptType.INLINE; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -109,7 +107,7 @@ public class SecurityCachePermissionIT extends SecurityIntegTestCase { //Template template = new Template(source, INLINE, MustacheScriptEngineService.NAME, null, singletonMap("name", "token")); SearchResponse response = client().prepareSearch("data").setTypes("a") - .setQuery(QueryBuilders.templateQuery(source, singletonMap("name", "token"))) + .setQuery(new TemplateQueryBuilder(source, ScriptService.ScriptType.INLINE, singletonMap("name", "token"))) .execute().actionGet(); assertThat(response.isTimedOut(), is(false)); assertThat(response.getHits().hits().length, is(1)); @@ -119,7 +117,7 @@ public class SecurityCachePermissionIT extends SecurityIntegTestCase { .filterWithHeader(singletonMap("Authorization", basicAuthHeaderValue(READ_ONE_IDX_USER, new SecuredString("changeme".toCharArray())))) .prepareSearch("data").setTypes("a") - .setQuery(QueryBuilders.templateQuery(source, singletonMap("name", "token"))) + .setQuery(new TemplateQueryBuilder(source, ScriptService.ScriptType.INLINE, singletonMap("name", "token"))) .execute().actionGet()); assertThat(e.toString(), containsString("ElasticsearchSecurityException[action")); assertThat(e.toString(), containsString("unauthorized")); diff --git a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/WatchAckIT.java b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/WatchAckIT.java index 77774111be4..0669ae7ad43 100644 --- a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/WatchAckIT.java +++ b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/WatchAckIT.java @@ -158,7 +158,7 @@ public class WatchAckIT extends AbstractWatcherIntegrationTestCase { assertThat(parsedWatch.status().actionStatus("_a2").ackStatus().state(), is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); - long throttledCount = docCount(HistoryStore.INDEX_PREFIX + "*", null, + long throttledCount = docCount(HistoryStore.INDEX_PREFIX_WITH_TEMPLATE + "*", null, matchQuery(WatchRecord.Field.STATE.getPreferredName(), ExecutionState.THROTTLED.id())); assertThat(throttledCount, greaterThan(0L)); } @@ -240,7 +240,7 @@ public class WatchAckIT extends AbstractWatcherIntegrationTestCase { assertThat(parsedWatch.status().actionStatus("_a2").ackStatus().state(), is(ActionStatus.AckStatus.State.AWAITS_SUCCESSFUL_EXECUTION)); - long throttledCount = docCount(HistoryStore.INDEX_PREFIX + "*", null, + long throttledCount = docCount(HistoryStore.INDEX_PREFIX_WITH_TEMPLATE + "*", null, matchQuery(WatchRecord.Field.STATE.getPreferredName(), ExecutionState.THROTTLED.id())); assertThat(throttledCount, greaterThan(0L)); } diff --git a/elasticsearch/qa/reindex-tests-with-security/build.gradle b/elasticsearch/qa/reindex-tests-with-security/build.gradle index be28f4d6e46..3beea6c57f7 100644 --- a/elasticsearch/qa/reindex-tests-with-security/build.gradle +++ b/elasticsearch/qa/reindex-tests-with-security/build.gradle @@ -7,7 +7,7 @@ dependencies { integTest { cluster { setting 'script.inline', 'true' - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' extraConfigFile 'x-pack/roles.yml', 'roles.yml' [ test_admin: 'superuser', diff --git a/elasticsearch/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/RestIT.java b/elasticsearch/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityClientYamlTestSuiteIT.java similarity index 73% rename from elasticsearch/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/RestIT.java rename to elasticsearch/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityClientYamlTestSuiteIT.java index 1b726fa386a..0fc19624914 100644 --- a/elasticsearch/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/RestIT.java +++ b/elasticsearch/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityClientYamlTestSuiteIT.java @@ -7,28 +7,29 @@ package org.elasticsearch.xpack.security; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class RestIT extends ESRestTestCase { +public class ReindexWithSecurityClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String USER = "test_admin"; private static final String PASS = "changeme"; - public RestIT(@Name("yaml") RestTestCandidate testCandidate) { + public ReindexWithSecurityClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } /** diff --git a/elasticsearch/qa/security-client-tests/build.gradle b/elasticsearch/qa/security-client-tests/build.gradle index 2b0a08397a1..ec4d3ff3c3b 100644 --- a/elasticsearch/qa/security-client-tests/build.gradle +++ b/elasticsearch/qa/security-client-tests/build.gradle @@ -14,7 +14,7 @@ project.sourceSets.test.output.dir(outputDir, builtBy: copyXPackPluginProps) integTest { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setupCommand 'setupDummyUser', 'bin/x-pack/users', 'useradd', 'test_user', '-p', 'changeme', '-r', 'superuser' setupCommand 'setupTransportClientUser', diff --git a/elasticsearch/qa/security-client-tests/src/test/java/org/elasticsearch/xpack/security/qa/SecurityTransportClientIT.java b/elasticsearch/qa/security-client-tests/src/test/java/org/elasticsearch/xpack/security/qa/SecurityTransportClientIT.java index c9cf99d50fa..0a4f14ae817 100644 --- a/elasticsearch/qa/security-client-tests/src/test/java/org/elasticsearch/xpack/security/qa/SecurityTransportClientIT.java +++ b/elasticsearch/qa/security-client-tests/src/test/java/org/elasticsearch/xpack/security/qa/SecurityTransportClientIT.java @@ -13,6 +13,7 @@ import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.transport.MockTransportClient; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.test.ESIntegTestCase; @@ -115,6 +116,6 @@ public class SecurityTransportClientIT extends ESIntegTestCase { .put("cluster.name", clusterName) .build(); - return TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build().addTransportAddress(publishAddress); + return new MockTransportClient(settings, XPackPlugin.class).addTransportAddress(publishAddress); } } diff --git a/elasticsearch/qa/security-example-realm/build.gradle b/elasticsearch/qa/security-example-realm/build.gradle index f7081642bee..bc0cba826a4 100644 --- a/elasticsearch/qa/security-example-realm/build.gradle +++ b/elasticsearch/qa/security-example-realm/build.gradle @@ -43,7 +43,7 @@ task buildZip(type:Zip, dependsOn: [jar]) { task integTest(type: org.elasticsearch.gradle.test.RestIntegTestTask, dependsOn: buildZip) { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'xpack.security.authc.realms.custom.order', '0' setting 'xpack.security.authc.realms.custom.type', 'custom' setting 'xpack.security.authc.realms.esusers.order', '1' diff --git a/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java b/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java index e7df15dd2db..04948027f30 100644 --- a/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java +++ b/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/ExampleRealmExtension.java @@ -7,14 +7,27 @@ package org.elasticsearch.example; import org.elasticsearch.example.realm.CustomAuthenticationFailureHandler; import org.elasticsearch.example.realm.CustomRealm; -import org.elasticsearch.example.realm.CustomRealmFactory; -import org.elasticsearch.xpack.security.authc.AuthenticationModule; +import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.extensions.XPackExtension; +import org.elasticsearch.xpack.security.authc.Realm; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; public class ExampleRealmExtension extends XPackExtension { + + static { + // check that the extension's policy works. + AccessController.doPrivileged((PrivilegedAction) () -> { + System.getSecurityManager().checkPrintJobAccess(); + return null; + }); + } + @Override public String name() { return "custom realm example"; @@ -25,13 +38,18 @@ public class ExampleRealmExtension extends XPackExtension { return "a very basic implementation of a custom realm to validate it works"; } - public void onModule(AuthenticationModule authenticationModule) { - authenticationModule.addCustomRealm(CustomRealm.TYPE, CustomRealmFactory.class); - authenticationModule.setAuthenticationFailureHandler(CustomAuthenticationFailureHandler.class); - // check that the extension's policy works. - AccessController.doPrivileged((PrivilegedAction) () -> { - System.getSecurityManager().checkPrintJobAccess(); - return null; - }); + @Override + public Map getRealms() { + return Collections.singletonMap(CustomRealm.TYPE, CustomRealm::new); + } + + @Override + public AuthenticationFailureHandler getAuthenticationFailureHandler() { + return new CustomAuthenticationFailureHandler(); + } + + @Override + public Collection getRestHeaders() { + return Arrays.asList(CustomRealm.USER_HEADER, CustomRealm.PW_HEADER); } } diff --git a/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealm.java b/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealm.java index c6699f7b6ae..aee93517fdd 100644 --- a/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealm.java +++ b/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealm.java @@ -13,12 +13,12 @@ import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -public class CustomRealm extends Realm { +public class CustomRealm extends Realm { public static final String TYPE = "custom"; - static final String USER_HEADER = "User"; - static final String PW_HEADER = "Password"; + public static final String USER_HEADER = "User"; + public static final String PW_HEADER = "Password"; static final String KNOWN_USER = "custom_user"; static final String KNOWN_PW = "changeme"; @@ -46,7 +46,8 @@ public class CustomRealm extends Realm { } @Override - public User authenticate(UsernamePasswordToken token) { + public User authenticate(AuthenticationToken authToken) { + UsernamePasswordToken token = (UsernamePasswordToken)authToken; final String actualUser = token.principal(); if (KNOWN_USER.equals(actualUser) && SecuredString.constantTimeEquals(token.credentials(), KNOWN_PW)) { return new User(actualUser, ROLES); diff --git a/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealmFactory.java b/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealmFactory.java deleted file mode 100644 index 53dade409d8..00000000000 --- a/elasticsearch/qa/security-example-realm/src/main/java/org/elasticsearch/example/realm/CustomRealmFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.example.realm; - -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.rest.RestController; -import org.elasticsearch.xpack.security.authc.Realm; -import org.elasticsearch.xpack.security.authc.RealmConfig; - -public class CustomRealmFactory extends Realm.Factory { - - @Inject - public CustomRealmFactory(RestController controller) { - super(CustomRealm.TYPE, false); - controller.registerRelevantHeaders(CustomRealm.USER_HEADER, CustomRealm.PW_HEADER); - } - - @Override - public CustomRealm create(RealmConfig config) { - return new CustomRealm(config); - } - - @Override - public CustomRealm createDefault(String name) { - return null; - } -} diff --git a/elasticsearch/qa/security-example-realm/src/test/java/org/elasticsearch/example/realm/CustomRealmIT.java b/elasticsearch/qa/security-example-realm/src/test/java/org/elasticsearch/example/realm/CustomRealmIT.java index 1780052b191..c137e658bfa 100644 --- a/elasticsearch/qa/security-example-realm/src/test/java/org/elasticsearch/example/realm/CustomRealmIT.java +++ b/elasticsearch/qa/security-example-realm/src/test/java/org/elasticsearch/example/realm/CustomRealmIT.java @@ -20,7 +20,9 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -58,11 +60,10 @@ public class CustomRealmIT extends ESIntegTestCase { } public void testHttpAuthentication() throws Exception { - try (Response response = getRestClient().performRequest("GET", "/", + Response response = getRestClient().performRequest("GET", "/", new BasicHeader(CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER), - new BasicHeader(CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW))) { - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } + new BasicHeader(CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW)); + assertThat(response.getStatusLine().getStatusCode(), is(200)); } public void testTransportClient() throws Exception { @@ -78,7 +79,7 @@ public class CustomRealmIT extends ESIntegTestCase { .put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER) .put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW) .build(); - try (TransportClient client = TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build()) { + try (TransportClient client = new XPackTransportClient(settings)) { client.addTransportAddress(publishAddress); ClusterHealthResponse response = client.admin().cluster().prepareHealth().execute().actionGet(); assertThat(response.isTimedOut(), is(false)); @@ -98,7 +99,7 @@ public class CustomRealmIT extends ESIntegTestCase { .put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER + randomAsciiOfLength(1)) .put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW) .build(); - try (TransportClient client = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient client = new XPackTransportClient(settings)) { client.addTransportAddress(publishAddress); client.admin().cluster().prepareHealth().execute().actionGet(); fail("authentication failure should have resulted in a NoNodesAvailableException"); diff --git a/elasticsearch/qa/security-migrate-tests/build.gradle b/elasticsearch/qa/security-migrate-tests/build.gradle index 3cc6464e39e..308b4d3961c 100644 --- a/elasticsearch/qa/security-migrate-tests/build.gradle +++ b/elasticsearch/qa/security-migrate-tests/build.gradle @@ -7,7 +7,7 @@ dependencies { integTest { cluster { setting 'script.inline', 'true' - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' extraConfigFile 'x-pack/roles.yml', 'roles.yml' [ test_admin: 'superuser', diff --git a/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java b/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java index 12cad15d5e6..00361ffefe2 100644 --- a/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java +++ b/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java @@ -118,6 +118,13 @@ public class MigrateToolIT extends MigrateToolTestCase { String token = basicAuthHeaderValue("bob", new SecuredString("changeme".toCharArray())); // Create "index1" index and try to search from it as "bob" client.filterWithHeader(Collections.singletonMap("Authorization", token)).admin().indices().prepareCreate("index1").get(); + // Wait for the index to be ready so it doesn't fail if no shards are initialized + client.admin().cluster().health(Requests.clusterHealthRequest("index1") + .timeout(TimeValue.timeValueSeconds(30)) + .waitForYellowStatus() + .waitForEvents(Priority.LANGUID) + .waitForRelocatingShards(0)) + .actionGet(); SearchResponse searchResp = client.filterWithHeader(Collections.singletonMap("Authorization", token)).prepareSearch("index1").get(); } } diff --git a/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolTestCase.java b/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolTestCase.java index 76f57f802ff..a25c950221c 100644 --- a/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolTestCase.java +++ b/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolTestCase.java @@ -14,9 +14,8 @@ import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; -import org.elasticsearch.node.internal.InternalSettingsPreparer; -import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.xpack.XPackTransportClient; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -25,10 +24,8 @@ import org.junit.BeforeClass; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.file.Path; -import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; -import static com.carrotsearch.randomizedtesting.RandomizedTest.randomAsciiOfLength; import static org.hamcrest.Matchers.notNullValue; /** @@ -45,6 +42,7 @@ import static org.hamcrest.Matchers.notNullValue; * your test. */ @LuceneTestCase.SuppressSysoutChecks(bugUrl = "we log a lot on purpose") +@ESIntegTestCase.SuppressLocalMode public abstract class MigrateToolTestCase extends LuceneTestCase { /** @@ -77,13 +75,9 @@ public abstract class MigrateToolTestCase extends LuceneTestCase { .put("client.transport.ignore_cluster_name", true) .put("path.home", tempDir) .put(Security.USER_SETTING.getKey(), "transport_user:changeme") - .put("node.mode", "network") // we require network here! .build(); - TransportClient.Builder transportClientBuilder = TransportClient.builder() - .addPlugin(XPackPlugin.class) - .settings(clientSettings); - TransportClient client = transportClientBuilder.build().addTransportAddresses(transportAddresses); + TransportClient client = new XPackTransportClient(clientSettings).addTransportAddresses(transportAddresses); logger.info("--> Elasticsearch Java TransportClient started"); diff --git a/elasticsearch/qa/smoke-test-graph-with-security/build.gradle b/elasticsearch/qa/smoke-test-graph-with-security/build.gradle index 820f387a853..564b71864cd 100644 --- a/elasticsearch/qa/smoke-test-graph-with-security/build.gradle +++ b/elasticsearch/qa/smoke-test-graph-with-security/build.gradle @@ -15,7 +15,7 @@ integTest { dependsOn copyGraphRestTests cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' extraConfigFile 'x-pack/roles.yml', 'roles.yml' setupCommand 'setupTestAdminUser', 'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'changeme', '-r', 'superuser' diff --git a/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityIT.java b/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityIT.java index 01556a37fde..50c81911c21 100644 --- a/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityIT.java +++ b/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityIT.java @@ -7,31 +7,31 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; - +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class GraphWithSecurityIT extends ESRestTestCase { +public class GraphWithSecurityIT extends ESClientYamlSuiteTestCase { private static final String TEST_ADMIN_USERNAME = "test_admin"; private static final String TEST_ADMIN_PASSWORD = "changeme"; - public GraphWithSecurityIT(@Name("yaml") RestTestCandidate testCandidate) { + public GraphWithSecurityIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } protected String[] getCredentials() { diff --git a/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityInsufficientRoleIT.java b/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityInsufficientRoleIT.java index 18f41655e4f..8cb3c040050 100644 --- a/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityInsufficientRoleIT.java +++ b/elasticsearch/qa/smoke-test-graph-with-security/src/test/java/org/elasticsearch/smoketest/GraphWithSecurityInsufficientRoleIT.java @@ -6,7 +6,8 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; -import org.elasticsearch.test.rest.RestTestCandidate; + +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; import java.io.IOException; @@ -14,14 +15,14 @@ import static org.hamcrest.Matchers.containsString; public class GraphWithSecurityInsufficientRoleIT extends GraphWithSecurityIT { - public GraphWithSecurityInsufficientRoleIT(@Name("yaml") RestTestCandidate testCandidate) { + public GraphWithSecurityInsufficientRoleIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } public void test() throws IOException { try { super.test(); - fail(); + fail("should have failed because of missing role"); } catch(AssertionError ae) { assertThat(ae.getMessage(), containsString("action [indices:data/read/xpack/graph/explore")); assertThat(ae.getMessage(), containsString("returned [403 Forbidden]")); diff --git a/elasticsearch/qa/smoke-test-monitoring-with-security/build.gradle b/elasticsearch/qa/smoke-test-monitoring-with-security/build.gradle index da9d76bf26c..bbaf3115dc6 100644 --- a/elasticsearch/qa/smoke-test-monitoring-with-security/build.gradle +++ b/elasticsearch/qa/smoke-test-monitoring-with-security/build.gradle @@ -17,7 +17,7 @@ subprojects { cluster { systemProperty 'es.logger.level', 'TRACE' - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'xpack.monitoring.collection.interval', '3s' extraConfigFile 'x-pack/roles.yml', '../roles.yml' setupCommand 'setupTestAdminUser', diff --git a/elasticsearch/qa/smoke-test-monitoring-with-security/insufficient-rights/src/test/java/org/elasticsearch/smoketest/MonitoringWithSecurityInsufficientRoleIT.java b/elasticsearch/qa/smoke-test-monitoring-with-security/insufficient-rights/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT.java similarity index 79% rename from elasticsearch/qa/smoke-test-monitoring-with-security/insufficient-rights/src/test/java/org/elasticsearch/smoketest/MonitoringWithSecurityInsufficientRoleIT.java rename to elasticsearch/qa/smoke-test-monitoring-with-security/insufficient-rights/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT.java index 037941611bf..b9ccfd6c897 100644 --- a/elasticsearch/qa/smoke-test-monitoring-with-security/insufficient-rights/src/test/java/org/elasticsearch/smoketest/MonitoringWithSecurityInsufficientRoleIT.java +++ b/elasticsearch/qa/smoke-test-monitoring-with-security/insufficient-rights/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT.java @@ -7,27 +7,28 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.containsString; -public class MonitoringWithSecurityInsufficientRoleIT extends ESRestTestCase { +public class SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { - public MonitoringWithSecurityInsufficientRoleIT(@Name("yaml") RestTestCandidate testCandidate) { + public SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Override diff --git a/elasticsearch/qa/smoke-test-monitoring-with-security/sufficient-rights/src/test/java/org/elasticsearch/smoketest/MonitoringWithSecurityIT.java b/elasticsearch/qa/smoke-test-monitoring-with-security/sufficient-rights/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT.java similarity index 73% rename from elasticsearch/qa/smoke-test-monitoring-with-security/sufficient-rights/src/test/java/org/elasticsearch/smoketest/MonitoringWithSecurityIT.java rename to elasticsearch/qa/smoke-test-monitoring-with-security/sufficient-rights/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT.java index c98a6d01c38..f69d4d7657b 100644 --- a/elasticsearch/qa/smoke-test-monitoring-with-security/sufficient-rights/src/test/java/org/elasticsearch/smoketest/MonitoringWithSecurityIT.java +++ b/elasticsearch/qa/smoke-test-monitoring-with-security/sufficient-rights/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT.java @@ -7,26 +7,27 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class MonitoringWithSecurityIT extends ESRestTestCase { +public class SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { - public MonitoringWithSecurityIT(@Name("yaml") RestTestCandidate testCandidate) { + public SmokeTestMonitoringWithSecurityClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Override diff --git a/elasticsearch/qa/smoke-test-plugins-ssl/build.gradle b/elasticsearch/qa/smoke-test-plugins-ssl/build.gradle index fa3c5c588d3..d83db1a8deb 100644 --- a/elasticsearch/qa/smoke-test-plugins-ssl/build.gradle +++ b/elasticsearch/qa/smoke-test-plugins-ssl/build.gradle @@ -147,7 +147,7 @@ processTestResources.dependsOn( ext.pluginsCount = 1 // we install xpack explicitly project.rootProject.subprojects.findAll { it.path.startsWith(':plugins:') }.each { subproj -> // need to get a non-decorated project object, so must re-lookup the project by path - integTest.cluster.plugin(subproj.name, project(subproj.path)) + integTest.cluster.plugin(subproj.path) pluginsCount += 1 } @@ -166,7 +166,7 @@ integTest { setting 'xpack.security.ssl.keystore.path', nodeKeystore.name setting 'xpack.security.ssl.keystore.password', 'keypass' - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' // copy keystores into config/ extraConfigFile nodeKeystore.name, nodeKeystore diff --git a/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java b/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java index 67be47776af..b9e72faf3f3 100644 --- a/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java +++ b/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestMonitoringWithSecurityIT.java @@ -9,11 +9,12 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse; import org.elasticsearch.common.io.PathUtils; +import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.xpack.XPackPlugin; import org.junit.After; @@ -49,7 +50,6 @@ public class SmokeTestMonitoringWithSecurityIT extends ESIntegTestCase { private static final String USER = "test_user"; private static final String PASS = "changeme"; private static final String KEYSTORE_PASS = "keypass"; - private static final String MONITORING_PATTERN = ".monitoring-*"; @Override @@ -61,9 +61,10 @@ public class SmokeTestMonitoringWithSecurityIT extends ESIntegTestCase { protected Settings externalClusterClientSettings() { return Settings.builder() .put(Security.USER_SETTING.getKey(), USER + ":" + PASS) - .put(SecurityNettyTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty3Transport.SSL_SETTING.getKey(), true) .put("xpack.security.ssl.keystore.path", clientKeyStore) .put("xpack.security.ssl.keystore.password", KEYSTORE_PASS) + .put(NetworkModule.TRANSPORT_TYPE_KEY, Security.NAME) .build(); } diff --git a/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsSslIT.java b/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsSslClientYamlTestSuiteIT.java similarity index 71% rename from elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsSslIT.java rename to elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsSslClientYamlTestSuiteIT.java index 5741fb72acc..30cfabe500d 100644 --- a/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsSslIT.java +++ b/elasticsearch/qa/smoke-test-plugins-ssl/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsSslClientYamlTestSuiteIT.java @@ -7,15 +7,16 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.client.RestTestClient; -import org.elasticsearch.test.rest.parser.RestTestParseException; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; +import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -26,19 +27,19 @@ import java.nio.file.Path; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class SmokeTestPluginsSslIT extends ESRestTestCase { +public class SmokeTestPluginsSslClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String USER = "test_user"; private static final String PASS = "changeme"; private static final String KEYSTORE_PASS = "keypass"; - public SmokeTestPluginsSslIT(@Name("yaml") RestTestCandidate testCandidate) { + public SmokeTestPluginsSslClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } static Path keyStore; @@ -46,7 +47,7 @@ public class SmokeTestPluginsSslIT extends ESRestTestCase { @BeforeClass public static void getKeyStore() { try { - keyStore = PathUtils.get(SmokeTestPluginsSslIT.class.getResource("/test-node.jks").toURI()); + keyStore = PathUtils.get(SmokeTestPluginsSslClientYamlTestSuiteIT.class.getResource("/test-node.jks").toURI()); } catch (URISyntaxException e) { throw new ElasticsearchException("exception while reading the store", e); } @@ -65,9 +66,13 @@ public class SmokeTestPluginsSslIT extends ESRestTestCase { String token = basicAuthHeaderValue(USER, new SecuredString(PASS.toCharArray())); return Settings.builder() .put(ThreadContext.PREFIX + ".Authorization", token) - .put(RestTestClient.PROTOCOL, "https") - .put(RestTestClient.TRUSTSTORE_PATH, keyStore) - .put(RestTestClient.TRUSTSTORE_PASSWORD, KEYSTORE_PASS) + .put(ESRestTestCase.TRUSTSTORE_PATH, keyStore) + .put(ESRestTestCase.TRUSTSTORE_PASSWORD, KEYSTORE_PASS) .build(); } + + @Override + protected String getProtocol() { + return "https"; + } } diff --git a/elasticsearch/qa/smoke-test-plugins/build.gradle b/elasticsearch/qa/smoke-test-plugins/build.gradle index 4cb7890e84b..23330418e49 100644 --- a/elasticsearch/qa/smoke-test-plugins/build.gradle +++ b/elasticsearch/qa/smoke-test-plugins/build.gradle @@ -9,13 +9,13 @@ dependencies { ext.pluginsCount = 1 // we install xpack explicitly project.rootProject.subprojects.findAll { it.path.startsWith(':plugins:') }.each { subproj -> // need to get a non-decorated project object, so must re-lookup the project by path - integTest.cluster.plugin(subproj.name, project(subproj.path)) + integTest.cluster.plugin(subproj.path) pluginsCount += 1 } integTest { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setupCommand 'setupDummyUser', 'bin/x-pack/users', 'useradd', 'test_user', '-p', 'changeme', '-r', 'superuser' diff --git a/elasticsearch/qa/smoke-test-plugins/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsIT.java b/elasticsearch/qa/smoke-test-plugins/src/test/java/org/elasticsearch/smoketest/XSmokeTestPluginsClientYamlTestSuiteIT.java similarity index 71% rename from elasticsearch/qa/smoke-test-plugins/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsIT.java rename to elasticsearch/qa/smoke-test-plugins/src/test/java/org/elasticsearch/smoketest/XSmokeTestPluginsClientYamlTestSuiteIT.java index 4555cc858d2..384a1396d14 100644 --- a/elasticsearch/qa/smoke-test-plugins/src/test/java/org/elasticsearch/smoketest/SmokeTestPluginsIT.java +++ b/elasticsearch/qa/smoke-test-plugins/src/test/java/org/elasticsearch/smoketest/XSmokeTestPluginsClientYamlTestSuiteIT.java @@ -7,29 +7,30 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import java.io.IOException; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class SmokeTestPluginsIT extends ESRestTestCase { +public class XSmokeTestPluginsClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String USER = "test_user"; private static final String PASS = "changeme"; - public SmokeTestPluginsIT(@Name("yaml") RestTestCandidate testCandidate) { + public XSmokeTestPluginsClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Override diff --git a/elasticsearch/qa/smoke-test-security-with-mustache/build.gradle b/elasticsearch/qa/smoke-test-security-with-mustache/build.gradle new file mode 100644 index 00000000000..05ea5357e3e --- /dev/null +++ b/elasticsearch/qa/smoke-test-security-with-mustache/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'elasticsearch.rest-test' + +dependencies { + testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime') + testCompile project(path: ':modules:lang-mustache', configuration: 'runtime') +} + +integTest { + cluster { + plugin ':x-plugins:elasticsearch:x-pack' + setting 'xpack.watcher.enabled', 'false' + setting 'xpack.monitoring.enabled', 'false' + setting 'path.scripts', "${project.buildDir}/resources/test/templates" + setupCommand 'setupDummyUser', + 'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'changeme', '-r', 'superuser' + waitCondition = { node, ant -> + File tmpFile = new File(node.cwd, 'wait.success') + ant.get(src: "http://${node.httpUri()}", + dest: tmpFile.toString(), + username: 'test_admin', + password: 'changeme', + ignoreerrors: true, + retries: 10) + return tmpFile.exists() + } + } +} diff --git a/elasticsearch/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java new file mode 100644 index 00000000000..82075e09b3a --- /dev/null +++ b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.smoketest; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; +import org.elasticsearch.xpack.security.authc.support.SecuredString; + +import java.io.IOException; + +import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; + +public class SmokeTestSecurityWithMustacheClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { + + private static final String BASIC_AUTH_VALUE = basicAuthHeaderValue("test_admin", new SecuredString("changeme".toCharArray())); + + public SmokeTestSecurityWithMustacheClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + + @ParametersFactory + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); + } + + @Override + protected Settings restClientSettings() { + return Settings.builder() + .put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE) + .build(); + } +} diff --git a/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/rest-api-spec/test/templated_role_query/10_templated_role_query.yaml b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/rest-api-spec/test/templated_role_query/10_templated_role_query.yaml new file mode 100644 index 00000000000..a1e016aca5c --- /dev/null +++ b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/rest-api-spec/test/templated_role_query/10_templated_role_query.yaml @@ -0,0 +1,191 @@ +--- +setup: + - skip: + features: headers + + - do: + cluster.health: + wait_for_status: yellow + + - do: + xpack.security.put_user: + username: "inline_template_user" + body: > + { + "password": "changeme", + "roles" : [ "inline_template_role" ] + } + - do: + xpack.security.put_user: + username: "stored_template_user" + body: > + { + "password": "changeme", + "roles" : [ "stored_template_role" ] + } + + - do: + xpack.security.put_user: + username: "file_template_user" + body: > + { + "password": "changeme", + "roles" : [ "file_template_role" ] + } + + - do: + xpack.security.put_role: + name: "inline_template_role" + body: > + { + "indices": [ + { + "names": "foobar", + "privileges": ["all"], + "query" : { + "template" : { + "inline" : { + "term" : { "username" : "{{_user.username}}" } + } + } + } + } + ] + } + + - do: + xpack.security.put_role: + name: "stored_template_role" + body: > + { + "indices": [ + { + "names": "foobar", + "privileges": ["all"], + "query" : { + "template" : { + "id" : "1" + } + } + } + ] + } + + - do: + xpack.security.put_role: + name: "file_template_role" + body: > + { + "indices": [ + { + "names": "foobar", + "privileges": ["all"], + "query" : { + "template" : { + "file" : "query" + } + } + } + ] + } + + - do: + put_template: + id: "1" + body: > + { + "term" : { + "username" : "{{_user.username}}" + } + } + + - do: + index: + index: foobar + type: type + id: 1 + body: > + { + "username": "inline_template_user" + } + - do: + index: + index: foobar + type: type + id: 2 + body: > + { + "username": "stored_template_user" + } + - do: + index: + index: foobar + type: type + id: 3 + body: > + { + "username": "file_template_user" + } + + - do: + indices.refresh: {} + +--- +teardown: + - do: + xpack.security.delete_user: + username: "inline_template_user" + ignore: 404 + - do: + xpack.security.delete_user: + username: "stored_template_user" + ignore: 404 + - do: + xpack.security.delete_user: + username: "file_template_user" + ignore: 404 + - do: + xpack.security.delete_role: + name: "inline_template_role" + ignore: 404 + - do: + xpack.security.delete_role: + name: "stored_template_role" + ignore: 404 + - do: + xpack.security.delete_role: + name: "file_template_role" + ignore: 404 + +--- +"Test inline template": + - do: + headers: + Authorization: "Basic aW5saW5lX3RlbXBsYXRlX3VzZXI6Y2hhbmdlbWU=" + search: + index: foobar + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.username: inline_template_user} + +--- +"Test stored template": + - do: + headers: + Authorization: "Basic c3RvcmVkX3RlbXBsYXRlX3VzZXI6Y2hhbmdlbWU=" + search: + index: foobar + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.username: stored_template_user} + +--- +"Test file template": + - do: + headers: + Authorization: "Basic ZmlsZV90ZW1wbGF0ZV91c2VyOmNoYW5nZW1l" + search: + index: foobar + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.username: file_template_user} diff --git a/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/rest-api-spec/test/templated_role_query/20_small_users_one_index.yaml b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/rest-api-spec/test/templated_role_query/20_small_users_one_index.yaml new file mode 100644 index 00000000000..c04ae085fdd --- /dev/null +++ b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/rest-api-spec/test/templated_role_query/20_small_users_one_index.yaml @@ -0,0 +1,198 @@ +--- +setup: + - skip: + features: headers + + - do: + indices.create: + index: shared_logs + + - do: + cluster.health: + wait_for_status: yellow + - do: + ingest.put_pipeline: + id: "my_pipeline" + body: > + { + "processors": [ + { + "set_security_user" : { + "field" : "user" + } + } + ] + } + - do: + xpack.security.put_user: + username: "joe" + body: > + { + "password": "changeme", + "roles" : [ "small_companies_role" ], + "metadata" : { + "customer_id" : "1" + } + } + - do: + xpack.security.put_user: + username: "john" + body: > + { + "password": "changeme", + "roles" : [ "small_companies_role" ], + "metadata" : { + "customer_id" : "2" + } + } + +--- +teardown: + - do: + xpack.security.delete_user: + username: "joe" + ignore: 404 + - do: + xpack.security.delete_user: + username: "john" + ignore: 404 + - do: + xpack.security.delete_role: + name: "small_companies_role" + ignore: 404 + +--- +"Test shared index seperating user by using DLS role query with user's username": + - do: + xpack.security.put_role: + name: "small_companies_role" + body: > + { + "indices": [ + { + "names": "shared_logs", + "privileges": ["read", "create"], + "query" : { + "template" : { + "inline" : { + "term" : { "user.username" : "{{_user.username}}" } + } + } + } + } + ] + } + + - do: + headers: + Authorization: "Basic am9lOmNoYW5nZW1l" + index: + index: shared_logs + type: type + pipeline: "my_pipeline" + body: > + { + "log": "Joe's first log entry" + } + - do: + headers: + Authorization: "Basic am9objpjaGFuZ2VtZQ==" + index: + index: shared_logs + type: type + pipeline: "my_pipeline" + body: > + { + "log": "John's first log entry" + } + + - do: + indices.refresh: {} + + # Joe searches: + - do: + headers: + Authorization: "Basic am9lOmNoYW5nZW1l" + search: + index: shared_logs + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.user.username: joe} + + # John searches: + - do: + headers: + Authorization: "Basic am9objpjaGFuZ2VtZQ==" + search: + index: shared_logs + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.user.username: john} + +--- +"Test shared index seperating user by using DLS role query with user's metadata": + - do: + xpack.security.put_role: + name: "small_companies_role" + body: > + { + "indices": [ + { + "names": "shared_logs", + "privileges": ["read", "create"], + "query" : { + "template" : { + "inline" : { + "term" : { "user.metadata.customer_id" : "{{_user.metadata.customer_id}}" } + } + } + } + } + ] + } + + - do: + headers: + Authorization: "Basic am9lOmNoYW5nZW1l" + index: + index: shared_logs + type: type + pipeline: "my_pipeline" + body: > + { + "log": "Joe's first log entry" + } + - do: + headers: + Authorization: "Basic am9objpjaGFuZ2VtZQ==" + index: + index: shared_logs + type: type + pipeline: "my_pipeline" + body: > + { + "log": "John's first log entry" + } + + - do: + indices.refresh: {} + + # Joe searches: + - do: + headers: + Authorization: "Basic am9lOmNoYW5nZW1l" + search: + index: shared_logs + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.user.username: joe} + + # John searches: + - do: + headers: + Authorization: "Basic am9objpjaGFuZ2VtZQ==" + search: + index: shared_logs + body: { "query" : { "match_all" : {} } } + - match: { hits.total: 1} + - match: { hits.hits.0._source.user.username: john} diff --git a/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/templates/query.mustache b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/templates/query.mustache new file mode 100644 index 00000000000..34a93aa2cd7 --- /dev/null +++ b/elasticsearch/qa/smoke-test-security-with-mustache/src/test/resources/templates/query.mustache @@ -0,0 +1,5 @@ +{ + "term" : { + "username" : "{{_user.username}}" + } +} \ No newline at end of file diff --git a/elasticsearch/qa/smoke-test-watcher-with-groovy/build.gradle b/elasticsearch/qa/smoke-test-watcher-with-groovy/build.gradle index 22b8d482a14..75056113f39 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-groovy/build.gradle +++ b/elasticsearch/qa/smoke-test-watcher-with-groovy/build.gradle @@ -7,7 +7,7 @@ dependencies { integTest { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'script.inline', 'true' setting 'xpack.security.enabled', 'false' setting 'xpack.monitoring.enabled', 'false' diff --git a/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java b/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java index af80d098c1c..f181291db19 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java +++ b/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java @@ -7,9 +7,10 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; + +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.junit.After; import org.junit.Before; @@ -18,15 +19,15 @@ import java.io.IOException; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -public abstract class WatcherRestTestCase extends ESRestTestCase { +public abstract class WatcherRestTestCase extends ESClientYamlSuiteTestCase { - public WatcherRestTestCase(@Name("yaml") RestTestCandidate testCandidate) { + public WatcherRestTestCase(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Before diff --git a/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherWithGroovyIT.java b/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherWithGroovyIT.java index f4d1d4993a5..e218055c370 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherWithGroovyIT.java +++ b/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/java/org/elasticsearch/smoketest/WatcherWithGroovyIT.java @@ -5,12 +5,12 @@ */ package org.elasticsearch.smoketest; -import org.elasticsearch.test.rest.RestTestCandidate; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; /** Runs rest tests against external cluster */ public class WatcherWithGroovyIT extends WatcherRestTestCase { - public WatcherWithGroovyIT(RestTestCandidate testCandidate) { + public WatcherWithGroovyIT(ClientYamlTestCandidate testCandidate) { super(testCandidate); } diff --git a/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/resources/rest-api-spec/test/watcher_groovy/40_index_action.yaml b/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/resources/rest-api-spec/test/watcher_groovy/40_index_action.yaml new file mode 100644 index 00000000000..1c90ab603b6 --- /dev/null +++ b/elasticsearch/qa/smoke-test-watcher-with-groovy/src/test/resources/rest-api-spec/test/watcher_groovy/40_index_action.yaml @@ -0,0 +1,238 @@ +--- +"Test simple input to index action": + - do: + xpack.watcher.put_watch: + id: my_watch + body: > + { + "trigger" : { "schedule" : { "cron" : "0/1 * * * * ? 2020" } }, + "input" : { "simple" : { "foo": "bar" } }, + "actions" : { + "index_action" : { + "index" : { + "index" : "idx", + "doc_type" : "type", + "execution_time_field" : "@timestamp" + } + } + } + } + + - match: { _id: "my_watch" } + + + - do: + xpack.watcher.execute_watch: + id: "my_watch" + body: > + { + "trigger_data" : { + "triggered_time" : "2016-07-07T09:00:00Z", + "scheduled_time" : "2016-07-07T09:00:00Z" + } + } + + - match: { "watch_record.state": "executed" } + + - do: + indices.refresh: {} + + - do: + search: + index: idx + type: type + + - match: { hits.total: 1 } + - match: { hits.hits.0._source.foo: bar } + - gte: { hits.hits.0._source.@timestamp: '2016-07-08' } + +--- +"Test simple input with document field": + + - do: + xpack.watcher.put_watch: + id: my_watch + body: > + { + "trigger" : { "schedule" : { "cron" : "0/1 * * * * ? 2020" } }, + "input" : { "simple" : { "foo": "bar" } }, + "actions" : { + "index_action" : { + "transform" : { "script" : { "inline": "return [ '_doc' : ctx.payload ]" } }, + "index" : { + "index" : "idx", + "doc_type" : "type", + "execution_time_field" : "@timestamp" + } + } + } + } + + - match: { _id: "my_watch" } + + + - do: + xpack.watcher.execute_watch: + id: "my_watch" + body: > + { + "trigger_data" : { + "triggered_time" : "2016-07-07T09:00:00Z", + "scheduled_time" : "2016-07-07T09:00:00Z" + } + } + + - match: { "watch_record.state": "executed" } + + - do: + indices.refresh: {} + + - do: + search: + index: idx + type: type + + - match: { hits.total: 1 } + - match: { hits.hits.0._source.foo: bar } + - gte: { hits.hits.0._source.@timestamp: '2016-07-08"' } + + +--- +"Test simple input with wrong document results in error": + + - do: + xpack.watcher.put_watch: + id: my_watch + body: > + { + "trigger" : { "schedule" : { "cron" : "0/1 * * * * ? 2020" } }, + "input" : { "simple" : { "foo": "bar" } }, + "actions" : { + "index_action" : { + "transform" : { "script" : { "inline": "return [ '_doc' : 1 ]" } }, + "index" : { + "index" : "idx", + "doc_type" : "type", + "execution_time_field" : "@timestamp" + } + } + } + } + + - match: { _id: "my_watch" } + + + - do: + xpack.watcher.execute_watch: + id: "my_watch" + body: > + { + "record_execution" : true, + "trigger_data" : { + "triggered_time" : "2016-07-07T09:00:00Z", + "scheduled_time" : "2016-07-07T09:00:00Z" + } + } + + - match: { "watch_record.state": "executed" } + + - do: + indices.refresh: {} + + - do: + indices.exists: + index: idx + + - is_false: '' + + - do: + search: + index: .watcher-history-* + type: watch_record + body: > + { + "query" : { + "match" : { + "result.actions.status": "failure" + } + } + } + + - match: { hits.total: 1 } + +--- +"Test search input to index action with aggs": + + - do: + bulk: + refresh: true + body: + - '{"index": {"_index": "idx", "_type": "type", "_id": "1"}}' + - '{"@timestamp": "2016-07-07" }' + - '{"index": {"_index": "idx", "_type": "type", "_id": "2"}}' + - '{"@timestamp": "2016-07-08" }' + - '{"index": {"_index": "idx", "_type": "type", "_id": "3"}}' + - '{"@timestamp": "2016-07-09" }' + + - do: + xpack.watcher.put_watch: + id: my_watch + body: > + { + "trigger" : { "schedule" : { "cron" : "0/1 * * * * ? 2020" } }, + "input" : { + "search" : { + "request": { + "indices" : [ "idx" ], + "types" : [ "type" ], + "body" : { + "aggs" : { + "trend" : { + "date_histogram" : { + "field" : "@timestamp", + "interval" : "day" + } + } + } + } + } + } + }, + "actions" : { + "index_action" : { + "transform" : { "script" : { "inline": "return [ '_doc' : ctx.payload.aggregations.trend.buckets]" } }, + "index" : { + "index" : "idx", + "doc_type" : "bucket", + "execution_time_field" : "@timestamp" + } + } + } + } + + - match: { _id: "my_watch" } + + + - do: + xpack.watcher.execute_watch: + id: "my_watch" + body: > + { + "trigger_data" : { + "triggered_time" : "2016-07-07T09:00:00Z", + "scheduled_time" : "2016-07-07T09:00:00Z" + } + } + + - match: { "watch_record.state": "executed" } + + - do: + indices.refresh: {} + + - do: + search: + index: idx + type: bucket + + - match: { hits.total: 3 } + diff --git a/elasticsearch/qa/smoke-test-watcher-with-mustache/build.gradle b/elasticsearch/qa/smoke-test-watcher-with-mustache/build.gradle index 25831523b34..180295f0419 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-mustache/build.gradle +++ b/elasticsearch/qa/smoke-test-watcher-with-mustache/build.gradle @@ -7,7 +7,7 @@ dependencies { integTest { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'xpack.security.enabled', 'false' setting 'xpack.monitoring.enabled', 'false' setting 'http.port', '9400' diff --git a/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java b/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestWatchesWithMustacheClientYamlTestSuiteTestCase.java similarity index 65% rename from elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java rename to elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestWatchesWithMustacheClientYamlTestSuiteTestCase.java index af80d098c1c..412f1801770 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java +++ b/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestWatchesWithMustacheClientYamlTestSuiteTestCase.java @@ -7,9 +7,10 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; + +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.junit.After; import org.junit.Before; @@ -18,15 +19,15 @@ import java.io.IOException; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -public abstract class WatcherRestTestCase extends ESRestTestCase { +public abstract class SmokeTestWatchesWithMustacheClientYamlTestSuiteTestCase extends ESClientYamlSuiteTestCase { - public WatcherRestTestCase(@Name("yaml") RestTestCandidate testCandidate) { + public SmokeTestWatchesWithMustacheClientYamlTestSuiteTestCase(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Before diff --git a/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherTemplateTests.java b/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherTemplateTests.java index 3a90293ff75..a1bf631233e 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherTemplateTests.java +++ b/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherTemplateTests.java @@ -6,37 +6,31 @@ package org.elasticsearch.smoketest; import com.fasterxml.jackson.core.io.JsonStringEncoder; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.env.Environment; import org.elasticsearch.script.ScriptContextRegistry; import org.elasticsearch.script.ScriptEngineRegistry; -import org.elasticsearch.script.ScriptEngineService; import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptSettings; import org.elasticsearch.script.mustache.MustacheScriptEngineService; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.watcher.ResourceWatcherService; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.common.text.DefaultTextTemplateEngine; import org.elasticsearch.xpack.common.text.TextTemplate; import org.elasticsearch.xpack.common.text.TextTemplateEngine; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.junit.Before; import org.mockito.Mockito; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.Set; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; @@ -50,7 +44,7 @@ public class WatcherTemplateTests extends ESTestCase { Settings setting = Settings.builder().put(ScriptService.SCRIPT_AUTO_RELOAD_ENABLED_SETTING, true).build(); Environment environment = Mockito.mock(Environment.class); ResourceWatcherService resourceWatcherService = Mockito.mock(ResourceWatcherService.class); - ScriptContextRegistry registry = new ScriptContextRegistry(Collections.singletonList(ScriptServiceProxy.INSTANCE)); + ScriptContextRegistry registry = new ScriptContextRegistry(Collections.singletonList(WatcherScript.CTX_PLUGIN)); ScriptEngineRegistry scriptEngineRegistry = new ScriptEngineRegistry( Collections.singleton(new MustacheScriptEngineService(setting)) @@ -58,7 +52,7 @@ public class WatcherTemplateTests extends ESTestCase { ScriptSettings scriptSettings = new ScriptSettings(scriptEngineRegistry, registry); ScriptService scriptService = new ScriptService(setting, environment, resourceWatcherService, scriptEngineRegistry, registry, scriptSettings); - engine = new DefaultTextTemplateEngine(Settings.EMPTY, ScriptServiceProxy.of(scriptService)); + engine = new DefaultTextTemplateEngine(Settings.EMPTY, scriptService); } public void testEscaping() throws Exception { diff --git a/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherWithMustacheIT.java b/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherWithMustacheIT.java index 4865aa483ad..f02ba653c3b 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherWithMustacheIT.java +++ b/elasticsearch/qa/smoke-test-watcher-with-mustache/src/test/java/org/elasticsearch/smoketest/WatcherWithMustacheIT.java @@ -7,22 +7,23 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; + +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import java.io.IOException; /** Runs rest tests against external cluster */ -public class WatcherWithMustacheIT extends WatcherRestTestCase { +public class WatcherWithMustacheIT extends SmokeTestWatchesWithMustacheClientYamlTestSuiteTestCase { - public WatcherWithMustacheIT(@Name("yaml") RestTestCandidate testCandidate) { + public WatcherWithMustacheIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } } diff --git a/elasticsearch/qa/smoke-test-watcher-with-painless/build.gradle b/elasticsearch/qa/smoke-test-watcher-with-painless/build.gradle index 6f5114e88f0..239ca2f60dd 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-painless/build.gradle +++ b/elasticsearch/qa/smoke-test-watcher-with-painless/build.gradle @@ -7,7 +7,7 @@ dependencies { integTest { cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' setting 'script.inline', 'true' setting 'xpack.security.enabled', 'false' setting 'xpack.monitoring.enabled', 'false' diff --git a/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java b/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java index af80d098c1c..f181291db19 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java +++ b/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherRestTestCase.java @@ -7,9 +7,10 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; + +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.junit.After; import org.junit.Before; @@ -18,15 +19,15 @@ import java.io.IOException; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -public abstract class WatcherRestTestCase extends ESRestTestCase { +public abstract class WatcherRestTestCase extends ESClientYamlSuiteTestCase { - public WatcherRestTestCase(@Name("yaml") RestTestCandidate testCandidate) { + public WatcherRestTestCase(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Before diff --git a/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherWithPainlessIT.java b/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherWithPainlessIT.java index fa0db39e71b..f66f051049d 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherWithPainlessIT.java +++ b/elasticsearch/qa/smoke-test-watcher-with-painless/src/test/java/org/elasticsearch/smoketest/WatcherWithPainlessIT.java @@ -5,12 +5,12 @@ */ package org.elasticsearch.smoketest; -import org.elasticsearch.test.rest.RestTestCandidate; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; /** Runs rest tests against external cluster */ public class WatcherWithPainlessIT extends WatcherRestTestCase { - public WatcherWithPainlessIT(RestTestCandidate testCandidate) { + public WatcherWithPainlessIT(ClientYamlTestCandidate testCandidate) { super(testCandidate); } diff --git a/elasticsearch/qa/smoke-test-watcher-with-security/build.gradle b/elasticsearch/qa/smoke-test-watcher-with-security/build.gradle index 235cee460df..e0d5f8501d6 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-security/build.gradle +++ b/elasticsearch/qa/smoke-test-watcher-with-security/build.gradle @@ -19,7 +19,7 @@ integTest { 'getting_started/10_monitor_cluster_health/Getting started - Monitor cluster health'].join(',') cluster { - plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack') + plugin ':x-plugins:elasticsearch:x-pack' extraConfigFile 'x-pack/roles.yml', 'roles.yml' setupCommand 'setupTestAdminUser', 'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'changeme', '-r', 'superuser' diff --git a/elasticsearch/qa/smoke-test-watcher-with-security/src/test/java/org/elasticsearch/smoketest/WatcherWithSecurityIT.java b/elasticsearch/qa/smoke-test-watcher-with-security/src/test/java/org/elasticsearch/smoketest/SmokeTestWatcherWithSecurityClientYamlTestSuiteIT.java similarity index 80% rename from elasticsearch/qa/smoke-test-watcher-with-security/src/test/java/org/elasticsearch/smoketest/WatcherWithSecurityIT.java rename to elasticsearch/qa/smoke-test-watcher-with-security/src/test/java/org/elasticsearch/smoketest/SmokeTestWatcherWithSecurityClientYamlTestSuiteIT.java index 7c449e251b0..15999e8e5ef 100644 --- a/elasticsearch/qa/smoke-test-watcher-with-security/src/test/java/org/elasticsearch/smoketest/WatcherWithSecurityIT.java +++ b/elasticsearch/qa/smoke-test-watcher-with-security/src/test/java/org/elasticsearch/smoketest/SmokeTestWatcherWithSecurityClientYamlTestSuiteIT.java @@ -7,36 +7,35 @@ package org.elasticsearch.smoketest; import com.carrotsearch.randomizedtesting.annotations.Name; import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.test.rest.RestTestCandidate; -import org.elasticsearch.test.rest.parser.RestTestParseException; import org.junit.After; import org.junit.Before; - import java.io.IOException; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; - import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class WatcherWithSecurityIT extends ESRestTestCase { +public class SmokeTestWatcherWithSecurityClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String TEST_ADMIN_USERNAME = "test_admin"; private static final String TEST_ADMIN_PASSWORD = "changeme"; - public WatcherWithSecurityIT(@Name("yaml") RestTestCandidate testCandidate) { + public SmokeTestWatcherWithSecurityClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { super(testCandidate); } @ParametersFactory - public static Iterable parameters() throws IOException, RestTestParseException { - return ESRestTestCase.createParameters(0, 1); + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); } @Before diff --git a/elasticsearch/qa/smoke-test-watcher/build.gradle b/elasticsearch/qa/smoke-test-watcher/build.gradle new file mode 100644 index 00000000000..e8b5bbea9dd --- /dev/null +++ b/elasticsearch/qa/smoke-test-watcher/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'elasticsearch.rest-test' + +dependencies { + testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime') +} + +integTest { + cluster { + plugin ':x-plugins:elasticsearch:x-pack' + setting 'xpack.security.enabled', 'false' + setting 'xpack.monitoring.enabled', 'false' + setting 'http.port', '9400' + } +} diff --git a/elasticsearch/qa/smoke-test-watcher/src/test/java/org/elasticsearch/smoketest/SmokeTestWatcherClientYamlTestSuiteIT.java b/elasticsearch/qa/smoke-test-watcher/src/test/java/org/elasticsearch/smoketest/SmokeTestWatcherClientYamlTestSuiteIT.java new file mode 100644 index 00000000000..0032d8cdfa4 --- /dev/null +++ b/elasticsearch/qa/smoke-test-watcher/src/test/java/org/elasticsearch/smoketest/SmokeTestWatcherClientYamlTestSuiteIT.java @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.smoketest; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; +import org.elasticsearch.test.rest.yaml.parser.ClientYamlTestParseException; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; + +/** Runs rest tests against external cluster */ +public class SmokeTestWatcherClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { + + public SmokeTestWatcherClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + + @ParametersFactory + public static Iterable parameters() throws IOException, ClientYamlTestParseException { + return ESClientYamlSuiteTestCase.createParameters(0, 1); + } + + @Before + public void startWatcher() throws Exception { + getAdminExecutionContext().callApi("xpack.watcher.start", emptyMap(), emptyList(), emptyMap()); + } + + @After + public void stopWatcher() throws Exception { + getAdminExecutionContext().callApi("xpack.watcher.stop", emptyMap(), emptyList(), emptyMap()); + } +} diff --git a/elasticsearch/x-pack/watcher/src/test/resources/rest-api-spec/test/xpack/watcher/getting_started/10_monitor_cluster_health.yaml b/elasticsearch/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml similarity index 93% rename from elasticsearch/x-pack/watcher/src/test/resources/rest-api-spec/test/xpack/watcher/getting_started/10_monitor_cluster_health.yaml rename to elasticsearch/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml index e3353c4cf6c..871fba78e5f 100644 --- a/elasticsearch/x-pack/watcher/src/test/resources/rest-api-spec/test/xpack/watcher/getting_started/10_monitor_cluster_health.yaml +++ b/elasticsearch/qa/smoke-test-watcher/src/test/resources/rest-api-spec/test/watcher_getting_started/10_monitor_cluster_health.yaml @@ -1,3 +1,6 @@ +# This test has been moved out of the regular tests as it requires Elasticsearch to run on port 9400 +# That is a no-go for the clients testing, who could start Elasticsearch on arbitrary ports +# --- setup: - do: diff --git a/elasticsearch/x-pack/build.gradle b/elasticsearch/x-pack/build.gradle index cdb38bf8425..5c67a941b00 100644 --- a/elasticsearch/x-pack/build.gradle +++ b/elasticsearch/x-pack/build.gradle @@ -21,14 +21,20 @@ ext.compactProfile = 'full' dependencyLicenses.enabled = false +licenseHeaders { + approvedLicenses << 'BCrypt (BSD-like)' + additionalLicense 'BCRYP', 'BCrypt (BSD-like)', 'Copyright (c) 2006 Damien Miller ' +} + dependencies { // license deps compile project(':x-plugins:elasticsearch:license:base') testCompile project(':x-plugins:elasticsearch:license:licensor') // security deps + compile project(path: ':modules:transport-netty3', configuration: 'runtime') compile 'dk.brics.automaton:automaton:1.11-8' - compile 'com.unboundid:unboundid-ldapsdk:2.3.8' + compile 'com.unboundid:unboundid-ldapsdk:3.1.1' compile 'org.bouncycastle:bcprov-jdk15on:1.54' compile 'org.bouncycastle:bcpkix-jdk15on:1.54' testCompile 'com.google.jimfs:jimfs:1.1' @@ -231,13 +237,6 @@ thirdPartyAudit.excludes = [ 'javax.activation.UnsupportedDataTypeException' ] -// someone figure out what the x-plugins logic should be -licenseHeaders.enabled = false - -forbiddenApisMain { - signaturesURLs += [file('signatures.txt').toURI().toURL()] -} - modifyPom { MavenPom pom -> pom.withXml { XmlProvider xml -> // first find if we have dependencies at all, and grab the node diff --git a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/Graph.java b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/Graph.java index 9bc973d3219..23363ce9452 100644 --- a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/Graph.java +++ b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/Graph.java @@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.ActionPlugin; @@ -43,15 +44,9 @@ public class Graph extends Plugin implements ActionPlugin { } public Collection createGuiceModules() { - return Collections.singletonList(new GraphModule(enabled, transportClientMode)); - } - - @Override - public Collection> getGuiceServiceClasses() { - if (enabled == false|| transportClientMode) { - return Collections.emptyList(); - } - return Collections.singletonList(GraphLicensee.class); + return Collections.singletonList(b -> { + XPackPlugin.bindFeatureSet(b, GraphFeatureSet.class); + }); } @Override diff --git a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphFeatureSet.java b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphFeatureSet.java index 2cba904304c..ca7c9721115 100644 --- a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphFeatureSet.java +++ b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphFeatureSet.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.XPackFeatureSet; import java.io.IOException; @@ -20,12 +21,12 @@ import java.io.IOException; public class GraphFeatureSet implements XPackFeatureSet { private final boolean enabled; - private final GraphLicensee licensee; + private final XPackLicenseState licenseState; @Inject - public GraphFeatureSet(Settings settings, @Nullable GraphLicensee licensee, NamedWriteableRegistry namedWriteableRegistry) { + public GraphFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, NamedWriteableRegistry namedWriteableRegistry) { this.enabled = Graph.enabled(settings); - this.licensee = licensee; + this.licenseState = licenseState; namedWriteableRegistry.register(Usage.class, Usage.writeableName(Graph.NAME), Usage::new); } @@ -41,7 +42,7 @@ public class GraphFeatureSet implements XPackFeatureSet { @Override public boolean available() { - return licensee != null && licensee.isAvailable(); + return licenseState != null && licenseState.isGraphAllowed(); } @Override diff --git a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphLicensee.java b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphLicensee.java deleted file mode 100644 index 01bf5f8e536..00000000000 --- a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphLicensee.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.graph; - -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; - -public class GraphLicensee extends AbstractLicenseeComponent { - - public static final String ID = Graph.NAME; - - @Inject - public GraphLicensee(Settings settings, LicenseeRegistry clientService) { - super(settings, ID, clientService); - } - - @Override - public String[] expirationMessages() { - return new String[] { - "Graph explore APIs are disabled" - }; - } - - @Override - public String[] acknowledgmentMessages(License currentLicense, License newLicense) { - switch (newLicense.operationMode()) { - case BASIC: - case STANDARD: - case GOLD: - if (currentLicense != null) { - switch (currentLicense.operationMode()) { - case TRIAL: - case PLATINUM: - return new String[] { "Graph will be disabled" }; - } - } - break; - } - return Strings.EMPTY_ARRAY; - } - - /** - * Determine if Graph Exploration should be enabled. - *

- * Exploration is only disabled when the license has expired or if the mode is not: - *

    - *
  • {@link OperationMode#PLATINUM}
  • - *
  • {@link OperationMode#TRIAL}
  • - *
- * - * @return {@code true} as long as the license is valid. Otherwise {@code false}. - */ - public boolean isAvailable() { - // status is volatile - Status localStatus = status; - OperationMode operationMode = localStatus.getMode(); - - boolean licensed = operationMode == OperationMode.TRIAL || operationMode == OperationMode.PLATINUM; - - return licensed && localStatus.getLicenseState() != LicenseState.DISABLED; - } -} diff --git a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphModule.java b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphModule.java deleted file mode 100644 index f4108bcd90a..00000000000 --- a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/GraphModule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.graph; - -import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.inject.util.Providers; -import org.elasticsearch.xpack.XPackPlugin; - -/** - * - */ -public class GraphModule extends AbstractModule { - - private final boolean enabled; - private final boolean transportClientNode; - - public GraphModule(boolean enabled, boolean transportClientNode) { - this.enabled = enabled; - this.transportClientNode = transportClientNode; - } - - @Override - protected void configure() { - XPackPlugin.bindFeatureSet(binder(), GraphFeatureSet.class); - if (enabled && transportClientNode == false) { - bind(GraphLicensee.class).asEagerSingleton(); - } else { - bind(GraphLicensee.class).toProvider(Providers.of(null)); - } - } - -} diff --git a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/action/TransportGraphExploreAction.java b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/action/TransportGraphExploreAction.java index 2d58ac38282..4adaf31f2c2 100644 --- a/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/action/TransportGraphExploreAction.java +++ b/elasticsearch/x-pack/graph/src/main/java/org/elasticsearch/xpack/graph/action/TransportGraphExploreAction.java @@ -23,7 +23,8 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.license.plugin.core.LicenseUtils; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.bucket.sampler.DiversifiedAggregationBuilder; @@ -37,7 +38,7 @@ import org.elasticsearch.search.aggregations.bucket.terms.support.IncludeExclude import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.graph.GraphLicensee; +import org.elasticsearch.xpack.graph.Graph; import org.elasticsearch.xpack.graph.action.Connection.ConnectionId; import org.elasticsearch.xpack.graph.action.GraphExploreRequest.TermBoost; import org.elasticsearch.xpack.graph.action.Vertex.VertexId; @@ -58,7 +59,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class TransportGraphExploreAction extends HandledTransportAction { private final TransportSearchAction searchAction; - protected final GraphLicensee licensee; + protected final XPackLicenseState licenseState; static class VertexPriorityQueue extends PriorityQueue { @@ -76,19 +77,19 @@ public class TransportGraphExploreAction extends HandledTransportAction listener) { - if (licensee.isAvailable()) { + if (licenseState.isGraphAllowed()) { new AsyncGraphAction(request, listener).start(); } else { - listener.onFailure(LicenseUtils.newComplianceException(GraphLicensee.ID)); + listener.onFailure(LicenseUtils.newComplianceException(Graph.NAME)); } } diff --git a/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/GraphFeatureSetTests.java b/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/GraphFeatureSetTests.java index 56d3b949bbe..57e7ec8e60c 100644 --- a/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/GraphFeatureSetTests.java +++ b/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/GraphFeatureSetTests.java @@ -7,9 +7,8 @@ package org.elasticsearch.xpack.graph; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.graph.GraphFeatureSet; -import org.elasticsearch.xpack.graph.GraphLicensee; import org.junit.Before; import static org.hamcrest.core.Is.is; @@ -24,24 +23,24 @@ import static org.mockito.Mockito.when; */ public class GraphFeatureSetTests extends ESTestCase { - private GraphLicensee licensee; + private XPackLicenseState licenseState; private NamedWriteableRegistry namedWriteableRegistry; @Before public void init() throws Exception { - licensee = mock(GraphLicensee.class); + licenseState = mock(XPackLicenseState.class); namedWriteableRegistry = mock(NamedWriteableRegistry.class); } public void testWritableRegistration() throws Exception { - new GraphFeatureSet(Settings.EMPTY, licensee, namedWriteableRegistry); + new GraphFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry); verify(namedWriteableRegistry).register(eq(GraphFeatureSet.Usage.class), eq("xpack.usage.graph"), anyObject()); } public void testAvailable() throws Exception { - GraphFeatureSet featureSet = new GraphFeatureSet(Settings.EMPTY, licensee, namedWriteableRegistry); + GraphFeatureSet featureSet = new GraphFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry); boolean available = randomBoolean(); - when(licensee.isAvailable()).thenReturn(available); + when(licenseState.isGraphAllowed()).thenReturn(available); assertThat(featureSet.available(), is(available)); } @@ -55,7 +54,7 @@ public class GraphFeatureSetTests extends ESTestCase { } else { settings.put("xpack.graph.enabled", enabled); } - GraphFeatureSet featureSet = new GraphFeatureSet(settings.build(), licensee, namedWriteableRegistry); + GraphFeatureSet featureSet = new GraphFeatureSet(settings.build(), licenseState, namedWriteableRegistry); assertThat(featureSet.enabled(), is(enabled)); } diff --git a/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/license/LicenseTests.java b/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/license/LicenseTests.java deleted file mode 100644 index 0ad59d8bc56..00000000000 --- a/elasticsearch/x-pack/graph/src/test/java/org/elasticsearch/xpack/graph/license/LicenseTests.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.graph.license; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.AbstractLicenseeTestCase; -import org.elasticsearch.xpack.graph.GraphLicensee; - -import static org.hamcrest.Matchers.is; - -public class LicenseTests extends AbstractLicenseeTestCase { - - private SimpleLicenseeRegistry licenseeRegistry = new SimpleLicenseeRegistry(); - - public void testPlatinumTrialLicenseCanDoEverything() throws Exception { - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicensePlatinumTrialBehaviour(graphLicensee); - } - - public void testBasicLicenseIsDisabled() throws Exception { - licenseeRegistry.setOperationMode(OperationMode.BASIC); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testStandardLicenseIsDisabled() throws Exception { - licenseeRegistry.setOperationMode(OperationMode.STANDARD); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testNoLicenseDoesNotWork() { - licenseeRegistry.setOperationMode(OperationMode.BASIC); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - licenseeRegistry.disable(); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testExpiredPlatinumTrialLicenseIsRestricted() throws Exception { - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - licenseeRegistry.disable(); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testUpgradingFromBasicLicenseWorks() { - licenseeRegistry.setOperationMode(OperationMode.BASIC); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - assertLicensePlatinumTrialBehaviour(graphLicensee); - } - - public void testDowngradingToBasicLicenseWorks() { - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicensePlatinumTrialBehaviour(graphLicensee); - - licenseeRegistry.setOperationMode(OperationMode.BASIC); - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testUpgradingFromStandardLicenseWorks() { - licenseeRegistry.setOperationMode(OperationMode.STANDARD); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - assertLicensePlatinumTrialBehaviour(graphLicensee); - } - - public void testDowngradingToStandardLicenseWorks() { - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicensePlatinumTrialBehaviour(graphLicensee); - - licenseeRegistry.setOperationMode(OperationMode.STANDARD); - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testDowngradingToGoldLicenseWorks() { - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - - assertLicensePlatinumTrialBehaviour(graphLicensee); - - licenseeRegistry.setOperationMode(OperationMode.GOLD); - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - } - - public void testUpgradingExpiredLicenseWorks() { - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - GraphLicensee graphLicensee = new GraphLicensee(Settings.EMPTY, licenseeRegistry); - licenseeRegistry.register(graphLicensee); - licenseeRegistry.disable(); - - assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(graphLicensee); - - licenseeRegistry.setOperationMode(randomTrialOrPlatinumMode()); - assertLicensePlatinumTrialBehaviour(graphLicensee); - } - - private void assertLicensePlatinumTrialBehaviour(GraphLicensee graphLicensee) { - assertThat("Expected graph exploration to be allowed", graphLicensee.isAvailable(), is(true)); - } - - private void assertLicenseBasicOrStandardGoldOrNoneOrExpiredBehaviour(GraphLicensee graphLicensee) { - assertThat("Expected graph exploration not to be allowed", graphLicensee.isAvailable(), is(false)); - } -} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseAction.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseAction.java index 9bb72c3da4a..de356870fbb 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseAction.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.delete; +package org.elasticsearch.license; import org.elasticsearch.action.Action; import org.elasticsearch.client.ElasticsearchClient; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseRequest.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseRequest.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseRequest.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseRequest.java index da52e8be014..29558cf9e42 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseRequest.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseRequest.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.delete; +package org.elasticsearch.license; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.master.AcknowledgedRequest; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseRequestBuilder.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseRequestBuilder.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseRequestBuilder.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseRequestBuilder.java index bbdc2574ca9..b554b005537 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseRequestBuilder.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseRequestBuilder.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.delete; +package org.elasticsearch.license; import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseResponse.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseResponse.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseResponse.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseResponse.java index 2b5d35edeae..c30890a0ff6 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/DeleteLicenseResponse.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/DeleteLicenseResponse.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.delete; +package org.elasticsearch.license; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.common.io.stream.StreamInput; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/ExpirationCallback.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/ExpirationCallback.java similarity index 98% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/ExpirationCallback.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/ExpirationCallback.java index 4166ce3d423..4aab4114b01 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/ExpirationCallback.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/ExpirationCallback.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.unit.TimeValue; @@ -12,7 +12,7 @@ import org.elasticsearch.xpack.scheduler.SchedulerEngine; import java.util.UUID; -public abstract class ExpirationCallback { +abstract class ExpirationCallback { static final String EXPIRATION_JOB_PREFIX = ".license_expiration_job_"; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseAction.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseAction.java index 805a45df6e6..47263410796 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseAction.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.get; +package org.elasticsearch.license; import org.elasticsearch.action.Action; import org.elasticsearch.client.ElasticsearchClient; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseRequest.java similarity index 91% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseRequest.java index 499fdc68c2b..965308de0e4 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequest.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseRequest.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.get; +package org.elasticsearch.license; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.master.MasterNodeReadRequest; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseRequestBuilder.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseRequestBuilder.java index 5b89c6a619c..7e92a54bce2 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseRequestBuilder.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseRequestBuilder.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.get; +package org.elasticsearch.license; import org.elasticsearch.action.support.master.MasterNodeReadOperationRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseResponse.java similarity index 95% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseResponse.java index 1d5b9a67cdf..171ccbbbdbb 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/GetLicenseResponse.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.get; +package org.elasticsearch.license; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.common.io.stream.StreamInput; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java similarity index 51% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java index 138f4c341a3..e1486dd119e 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; @@ -18,18 +18,18 @@ import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.Lifecycle; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.env.Environment; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.license.core.License; import org.elasticsearch.license.core.LicenseVerifier; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; +import org.elasticsearch.license.core.OperationModeFileWatcher; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.scheduler.SchedulerEngine; import org.elasticsearch.xpack.support.clock.Clock; @@ -40,41 +40,29 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; /** - * Service responsible for managing {@link LicensesMetaData} - * Interfaces through which this is exposed are: - * - LicensesManagerService - responsible for managing signed and one-time-trial licenses - * - LicensesClientService - responsible for listener registration of consumer plugin(s) + * Service responsible for managing {@link LicensesMetaData}. *

- * Registration Scheme: - *

- * A consumer plugin is registered with {@link LicenseeRegistry#register(Licensee)} - * This method can be called at any time during the life-cycle of the consumer plugin. - * If the listener can not be registered immediately, it is queued up and registered on the first clusterChanged event with - * no {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK} block - * Upon successful registration, the listeners are notified appropriately using the notification scheme - *

- * Notification Scheme: - *

- * All registered listeners are notified of the current license upon registration or when a new license is installed in the cluster state. - * When a new license is notified as enabled to the registered listener, a notification is scheduled at the time of license expiry. - * Registered listeners are notified using {@link #onUpdate(LicensesMetaData)} + * On the master node, the service handles updating the cluster state when a new license is registered. + * It also listens on all nodes for cluster state updates, and updates {@link XPackLicenseState} when + * the license changes are detected in the cluster state. */ -public class LicensesService extends AbstractLifecycleComponent implements ClusterStateListener, LicensesManagerService, - LicenseeRegistry, SchedulerEngine.Listener { +public class LicenseService extends AbstractLifecycleComponent implements ClusterStateListener, SchedulerEngine.Listener { // pkg private for tests static final TimeValue TRIAL_LICENSE_DURATION = TimeValue.timeValueHours(30 * 24); + /** + * Duration of grace period after a license has expired + */ + static final TimeValue GRACE_PERIOD_DURATION = days(7); + private final ClusterService clusterService; - /** - * Currently active consumers to notify to - */ - private final List registeredLicensees = new CopyOnWriteArrayList<>(); + /** The xpack feature state to update when license changes are made. */ + private final XPackLicenseState licenseState; /** * Currently active license @@ -83,6 +71,11 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust private SchedulerEngine scheduler; private final Clock clock; + /** + * File watcher for operation mode changes + */ + private final OperationModeFileWatcher operationModeFileWatcher; + /** * Callbacks to notify relative to license expiry */ @@ -93,125 +86,78 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust */ private int trialLicenseMaxNodes = 1000; - /** - * Duration of grace period after a license has expired - */ - public static final TimeValue GRACE_PERIOD_DURATION = days(7); - - private static final String LICENSE_JOB = "licenseJob"; + public static final String LICENSE_JOB = "licenseJob"; private static final FormatDateTimeFormatter DATE_FORMATTER = Joda.forPattern("EEEE, MMMMM dd, yyyy", Locale.ROOT); private static final String ACKNOWLEDGEMENT_HEADER = "This license update requires acknowledgement. To acknowledge the license, " + "please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:"; - @Inject - public LicensesService(Settings settings, ClusterService clusterService, Clock clock) { + public LicenseService(Settings settings, ClusterService clusterService, Clock clock, Environment env, + ResourceWatcherService resourceWatcherService, XPackLicenseState licenseState) { super(settings); this.clusterService = clusterService; - populateExpirationCallbacks(); this.clock = clock; this.scheduler = new SchedulerEngine(clock); + this.licenseState = licenseState; + this.operationModeFileWatcher = new OperationModeFileWatcher(resourceWatcherService, + XPackPlugin.resolveConfigFile(env, "license_mode"), logger, () -> updateLicenseState(getLicense())); this.scheduler.register(this); + populateExpirationCallbacks(); + } + + private void logExpirationWarning(long expirationMillis, boolean expired) { + String expiredMsg = expired ? "will expire" : "expired"; + String general = LoggerMessageFormat.format(null, "\n" + + "#\n" + + "# License [{}] on [{}]. If you have a new license, please update it.\n" + + "# Otherwise, please reach out to your support contact.\n" + + "# ", expiredMsg, DATE_FORMATTER.printer().print(expirationMillis)); + if (expired) { + general = general.toUpperCase(Locale.ROOT); + } + StringBuilder builder = new StringBuilder(general); + builder.append(System.lineSeparator()); + if (expired) { + builder.append("# COMMERCIAL PLUGINS OPERATING WITH REDUCED FUNCTIONALITY"); + } else { + builder.append("# Commercial plugins operate with reduced functionality on license expiration:"); + } + XPackLicenseState.EXPIRATION_MESSAGES.forEach((feature, messages) -> { + if (messages.length > 0) { + builder.append(System.lineSeparator()); + builder.append("# - "); + builder.append(feature); + for (String message : messages) { + builder.append(System.lineSeparator()); + builder.append("# - "); + builder.append(message); + } + } + }); + logger.warn("{}", builder); } private void populateExpirationCallbacks() { expirationCallbacks.add(new ExpirationCallback.Pre(days(7), days(25), days(1)) { - @Override - public void on(License license) { - String general = LoggerMessageFormat.format(null, "\n" + - "#\n" + - "# License will expire on [{}]. If you have a new license, please update it.\n" + - "# Otherwise, please reach out to your support contact.\n" + - "# ", DATE_FORMATTER.printer().print(license.expiryDate())); - if (!registeredLicensees.isEmpty()) { - StringBuilder builder = new StringBuilder(general); - builder.append(System.lineSeparator()); - builder.append("# Commercial plugins operate with reduced functionality on license " + - "expiration:"); - for (InternalLicensee licensee : registeredLicensees) { - if (licensee.expirationMessages().length > 0) { - builder.append(System.lineSeparator()); - builder.append("# - "); - builder.append(licensee.id()); - for (String message : licensee.expirationMessages()) { - builder.append(System.lineSeparator()); - builder.append("# - "); - builder.append(message); - } - } - } - logger.warn("{}", builder); - } else { - logger.warn("{}", general); - } - } - } - ); + @Override + public void on(License license) { + logExpirationWarning(license.expiryDate(), false); + } + }); expirationCallbacks.add(new ExpirationCallback.Pre(days(0), days(7), TimeValue.timeValueMinutes(10)) { - @Override - public void on(License license) { - String general = LoggerMessageFormat.format(null, "\n" + - "#\n" + - "# License will expire on [{}]. If you have a new license, please update it.\n" + - "# Otherwise, please reach out to your support contact.\n" + - "# ", DATE_FORMATTER.printer().print(license.expiryDate())); - if (!registeredLicensees.isEmpty()) { - StringBuilder builder = new StringBuilder(general); - builder.append(System.lineSeparator()); - builder.append("# Commercial plugins operate with reduced functionality on license " + - "expiration:"); - for (InternalLicensee licensee : registeredLicensees) { - if (licensee.expirationMessages().length > 0) { - builder.append(System.lineSeparator()); - builder.append("# - "); - builder.append(licensee.id()); - for (String message : licensee.expirationMessages()) { - builder.append(System.lineSeparator()); - builder.append("# - "); - builder.append(message); - } - } - } - logger.warn("{}", builder.toString()); - } else { - logger.warn("{}", general); - } - } - } - ); + @Override + public void on(License license) { + logExpirationWarning(license.expiryDate(), false); + } + }); expirationCallbacks.add(new ExpirationCallback.Post(days(0), null, TimeValue.timeValueMinutes(10)) { - @Override - public void on(License license) { - // logged when grace period begins - String general = LoggerMessageFormat.format(null, "\n" + - "#\n" + - "# LICENSE EXPIRED ON [{}]. IF YOU HAVE A NEW LICENSE, PLEASE\n" + - "# UPDATE IT. OTHERWISE, PLEASE REACH OUT TO YOUR SUPPORT CONTACT.\n" + - "# ", DATE_FORMATTER.printer().print(license.expiryDate())); - if (!registeredLicensees.isEmpty()) { - StringBuilder builder = new StringBuilder(general); - builder.append(System.lineSeparator()); - builder.append("# COMMERCIAL PLUGINS OPERATING WITH REDUCED FUNCTIONALITY"); - for (InternalLicensee licensee : registeredLicensees) { - if (licensee.expirationMessages().length > 0) { - builder.append(System.lineSeparator()); - builder.append("# - "); - builder.append(licensee.id()); - for (String message : licensee.expirationMessages()) { - builder.append(System.lineSeparator()); - builder.append("# - "); - builder.append(message); - } - } - } - logger.warn("{}", builder.toString()); - } else { - logger.warn("{}", general); - } - } - } - ); + @Override + public void on(License license) { + // logged when grace period begins + logExpirationWarning(license.expiryDate(), true); + } + }); } /** @@ -227,22 +173,23 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust listener.onResponse(new PutLicenseResponse(true, LicensesStatus.EXPIRED)); } else { if (!request.acknowledged()) { + // TODO: ack messages should be generated on the master, since another node's cluster state may be behind... final License currentLicense = getLicense(); if (currentLicense != null) { - Map acknowledgeMessages = new HashMap<>(registeredLicensees.size() + 1); + Map acknowledgeMessages = new HashMap<>(); if (!License.isAutoGeneratedLicense(currentLicense.signature()) // current license is not auto-generated && currentLicense.issueDate() > newLicense.issueDate()) { // and has a later issue date - acknowledgeMessages.put("license", - new String[]{"The new license is older than the currently installed license. Are you sure you want to " + - "override the current license?"}); + acknowledgeMessages.put("license", new String[]{ + "The new license is older than the currently installed license. " + + "Are you sure you want to override the current license?"}); } - for (InternalLicensee licensee : registeredLicensees) { - String[] listenerAcknowledgeMessages = licensee.acknowledgmentMessages(currentLicense, newLicense); - if (listenerAcknowledgeMessages.length > 0) { - acknowledgeMessages.put(licensee.id(), listenerAcknowledgeMessages); + XPackLicenseState.ACKNOWLEDGMENT_MESSAGES.forEach((feature, ackMessages) -> { + String[] messages = ackMessages.apply(currentLicense.operationMode(), newLicense.operationMode()); + if (messages.length > 0) { + acknowledgeMessages.put(feature, messages); } - } - if (!acknowledgeMessages.isEmpty()) { + }); + if (acknowledgeMessages.isEmpty() == false) { // needs acknowledgement listener.onResponse(new PutLicenseResponse(false, LicensesStatus.VALID, ACKNOWLEDGEMENT_HEADER, acknowledgeMessages)); @@ -278,7 +225,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust if (licensesMetaData != null) { final License license = licensesMetaData.getLicense(); if (event.getJobName().equals(LICENSE_JOB)) { - notifyLicensees(license); + updateLicenseState(license); } else if (event.getJobName().startsWith(ExpirationCallback.EXPIRATION_JOB_PREFIX)) { expirationCallbacks.stream() .filter(expirationCallback -> expirationCallback.getId().equals(event.getJobName())) @@ -313,17 +260,6 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust }); } - @Override - public LicenseState licenseState() { - if (registeredLicensees.size() > 0) { - return registeredLicensees.get(0).currentLicenseState; - } else { - final License license = getLicense(clusterService.state().metaData().custom(LicensesMetaData.TYPE)); - return getLicenseState(license, clock.millis()); - } - } - - @Override public License getLicense() { final License license = getLicense(clusterService.state().metaData().custom(LicensesMetaData.TYPE)); return license == LicensesMetaData.LICENSE_TOMBSTONE ? null : license; @@ -377,15 +313,25 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust protected void doStart() throws ElasticsearchException { clusterService.add(this); scheduler.start(Collections.emptyList()); + logger.debug("initializing license state"); + final ClusterState clusterState = clusterService.state(); + if (clusterService.lifecycleState() == Lifecycle.State.STARTED + && clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) == false + && clusterState.nodes().getMasterNode() != null) { + final LicensesMetaData currentMetaData = clusterState.metaData().custom(LicensesMetaData.TYPE); + if (clusterState.getNodes().isLocalNodeElectedMaster() && + (currentMetaData == null || currentMetaData.getLicense() == null)) { + // triggers a cluster changed event + // eventually notifying the current licensee + registerTrialLicense(); + } + } } @Override protected void doStop() throws ElasticsearchException { clusterService.remove(this); scheduler.stop(); - // clear all handlers - registeredLicensees.clear(); - // clear current license currentLicense.set(null); } @@ -430,54 +376,30 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust } } - private void notifyLicensees(final License license) { + protected void updateLicenseState(final License license) { if (license == LicensesMetaData.LICENSE_TOMBSTONE) { // implies license has been explicitly deleted - // update licensee states - registeredLicensees.forEach(InternalLicensee::onRemove); + licenseState.update(License.OperationMode.MISSING, false); return; } if (license != null) { - logger.debug("notifying [{}] listeners", registeredLicensees.size()); - switch (getLicenseState(license, clock.millis())) { - case ENABLED: - for (InternalLicensee licensee : registeredLicensees) { - licensee.onChange(license, LicenseState.ENABLED); - } + long time = clock.millis(); + boolean active = time >= license.issueDate() && + time < license.expiryDate() + GRACE_PERIOD_DURATION.getMillis(); + licenseState.update(license.operationMode(), active); + + if (active) { + if (time < license.expiryDate()) { logger.debug("license [{}] - valid", license.uid()); - break; - case GRACE_PERIOD: - for (InternalLicensee licensee : registeredLicensees) { - licensee.onChange(license, LicenseState.GRACE_PERIOD); - } + } else { logger.warn("license [{}] - grace", license.uid()); - break; - case DISABLED: - for (InternalLicensee licensee : registeredLicensees) { - licensee.onChange(license, LicenseState.DISABLED); - } - logger.warn("license [{}] - expired", license.uid()); - break; + } + } else { + logger.warn("license [{}] - expired", license.uid()); } } } - static LicenseState getLicenseState(final License license, long time) { - if (license == null) { - return LicenseState.DISABLED; - } - if (license.issueDate() > time) { - return LicenseState.DISABLED; - } - if (license.expiryDate() > time) { - return LicenseState.ENABLED; - } - if ((license.expiryDate() + GRACE_PERIOD_DURATION.getMillis()) > time) { - return LicenseState.GRACE_PERIOD; - } - return LicenseState.DISABLED; - } - /** * Notifies registered licensees of license state change and/or new active license * based on the license in currentLicensesMetaData. @@ -489,42 +411,42 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust // license can be null if the trial license is yet to be auto-generated // in this case, it is a no-op if (license != null) { - notifyLicensees(license); - if (license.equals(currentLicense.get()) == false) { + final License previousLicense = currentLicense.get(); + if (license.equals(previousLicense) == false) { currentLicense.set(license); - scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, new LicenseSchedule(license))); + license.setOperationModeFileWatcher(operationModeFileWatcher); + scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, nextLicenseCheck(license))); for (ExpirationCallback expirationCallback : expirationCallbacks) { scheduler.add(new SchedulerEngine.Job(expirationCallback.getId(), (startTime, now) -> expirationCallback.nextScheduledTimeForExpiry(license.expiryDate(), startTime, now))); } + if (previousLicense != null) { + // remove operationModeFileWatcher to gc the old license object + previousLicense.removeOperationModeFileWatcher(); + } } + updateLicenseState(license); } } - @Override - public void register(Licensee licensee) { - for (final InternalLicensee existingLicensee : registeredLicensees) { - if (existingLicensee.id().equals(licensee.id())) { - throw new IllegalStateException("listener: [" + licensee.id() + "] has been already registered"); + // pkg private for tests + static SchedulerEngine.Schedule nextLicenseCheck(License license) { + return (startTime, time) -> { + if (time < license.issueDate()) { + // when we encounter a license with a future issue date + // which can happen with autogenerated license, + // we want to schedule a notification on the license issue date + // so the license is notificed once it is valid + // see https://github.com/elastic/x-plugins/issues/983 + return license.issueDate(); + } else if (time < license.expiryDate()) { + return license.expiryDate(); + } else if (time < license.expiryDate() + GRACE_PERIOD_DURATION.getMillis()) { + return license.expiryDate() + GRACE_PERIOD_DURATION.getMillis(); } - } - logger.debug("registering licensee [{}]", licensee.id()); - registeredLicensees.add(new InternalLicensee(licensee)); - final ClusterState clusterState = clusterService.state(); - if (clusterService.lifecycleState() == Lifecycle.State.STARTED - && clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) == false - && clusterState.nodes().getMasterNode() != null) { - final LicensesMetaData currentMetaData = clusterState.metaData().custom(LicensesMetaData.TYPE); - if (clusterState.getNodes().isLocalNodeElectedMaster() && - (currentMetaData == null || currentMetaData.getLicense() == null)) { - // triggers a cluster changed event - // eventually notifying the current licensee - registerTrialLicense(); - } else if (lifecycleState() == Lifecycle.State.STARTED) { - notifyLicensees(currentMetaData.getLicense()); - } - } + return -1; // license is expired, no need to check again + }; } License getLicense(final LicensesMetaData metaData) { @@ -532,7 +454,7 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust License license = metaData.getLicense(); if (license == LicensesMetaData.LICENSE_TOMBSTONE) { return license; - } else { + } else if (license != null) { boolean autoGeneratedLicense = License.isAutoGeneratedLicense(license.signature()); if ((autoGeneratedLicense && TrialLicense.verify(license)) || (!autoGeneratedLicense && LicenseVerifier.verifyLicense(license))) { @@ -542,58 +464,4 @@ public class LicensesService extends AbstractLifecycleComponent implements Clust } return null; } - - /** - * Stores acknowledgement, expiration and license notification callbacks - * for a registered listener - */ - private class InternalLicensee { - volatile License currentLicense = null; - volatile LicenseState currentLicenseState = LicenseState.DISABLED; - private final Licensee licensee; - - private InternalLicensee(Licensee licensee) { - this.licensee = licensee; - } - - @Override - public String toString() { - return "(listener: " + licensee.id() + ", state: " + currentLicenseState.name() + ")"; - } - - public String id() { - return licensee.id(); - } - - public String[] expirationMessages() { - return licensee.expirationMessages(); - } - - public String[] acknowledgmentMessages(License currentLicense, License newLicense) { - return licensee.acknowledgmentMessages(currentLicense, newLicense); - } - - public void onChange(License license, LicenseState state) { - synchronized (this) { - if (currentLicense == null // not yet initialized - || !currentLicense.equals(license) // current license has changed - || currentLicenseState != state) { // same license but state has changed - logger.debug("licensee [{}] notified", licensee.id()); - licensee.onChange(new Licensee.Status(license.operationMode(), state)); - currentLicense = license; - currentLicenseState = state; - } - } - } - - public void onRemove() { - synchronized (this) { - if (currentLicense != null || currentLicenseState != LicenseState.DISABLED) { - currentLicense = null; - currentLicenseState = LicenseState.DISABLED; - licensee.onChange(Licensee.Status.MISSING); - } - } - } - } } \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseUtils.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseUtils.java similarity index 96% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseUtils.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseUtils.java index 2f7a3ca7470..42139471646 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseUtils.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseUtils.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.rest.RestStatus; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensesMetaData.java similarity index 98% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensesMetaData.java index afba390049b..266d1c37aa9 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensesMetaData.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.apache.lucene.util.CollectionUtil; import org.elasticsearch.Version; @@ -32,7 +32,7 @@ import static org.elasticsearch.license.core.CryptUtils.encrypt; /** * Contains metadata about registered licenses */ -public class LicensesMetaData extends AbstractDiffable implements MetaData.Custom { +class LicensesMetaData extends AbstractDiffable implements MetaData.Custom { public static final String TYPE = "licenses"; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesStatus.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensesStatus.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesStatus.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensesStatus.java index 3bd18d58770..91e0d7239cf 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesStatus.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensesStatus.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; public enum LicensesStatus { VALID((byte) 0), diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/Licensing.java similarity index 57% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/Licensing.java index 037c7b3b206..1e118485f2c 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/Licensing.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/Licensing.java @@ -3,37 +3,26 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; - -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.common.component.LifecycleComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Module; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; -import org.elasticsearch.license.plugin.action.delete.TransportDeleteLicenseAction; -import org.elasticsearch.license.plugin.action.get.GetLicenseAction; -import org.elasticsearch.license.plugin.action.get.TransportGetLicenseAction; -import org.elasticsearch.license.plugin.action.put.PutLicenseAction; -import org.elasticsearch.license.plugin.action.put.TransportPutLicenseAction; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; -import org.elasticsearch.license.plugin.core.LicensesManagerService; -import org.elasticsearch.license.plugin.core.LicensesMetaData; -import org.elasticsearch.license.plugin.core.LicensesService; -import org.elasticsearch.license.plugin.rest.RestDeleteLicenseAction; -import org.elasticsearch.license.plugin.rest.RestGetLicenseAction; -import org.elasticsearch.license.plugin.rest.RestPutLicenseAction; -import org.elasticsearch.plugins.ActionPlugin; -import org.elasticsearch.rest.RestHandler; +package org.elasticsearch.license; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.plugins.ActionPlugin; +import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.support.clock.Clock; + import static java.util.Collections.emptyList; import static org.elasticsearch.xpack.XPackPlugin.isTribeNode; import static org.elasticsearch.xpack.XPackPlugin.transportClientMode; @@ -42,7 +31,8 @@ import static org.elasticsearch.xpack.XPackPlugin.transportClientMode; public class Licensing implements ActionPlugin { public static final String NAME = "license"; - private final boolean isTransportClient; + protected final Settings settings; + protected final boolean isTransportClient; private final boolean isTribeNode; static { @@ -50,10 +40,15 @@ public class Licensing implements ActionPlugin { } public Licensing(Settings settings) { + this.settings = settings; isTransportClient = transportClientMode(settings); isTribeNode = isTribeNode(settings); } + public Collection nodeModules() { + return Collections.emptyList(); + } + @Override public List, ? extends ActionResponse>> getActions() { if (isTribeNode) { @@ -74,22 +69,12 @@ public class Licensing implements ActionPlugin { RestDeleteLicenseAction.class); } - public Collection> nodeServices() { - if (isTransportClient == false && isTribeNode == false) { - return Collections.>singletonList(LicensesService.class); - } - return Collections.emptyList(); - } - - public Collection nodeModules() { - if (isTransportClient == false && isTribeNode == false) { - return Collections.singletonList(b -> { - b.bind(LicensesService.class).asEagerSingleton(); - b.bind(LicenseeRegistry.class).to(LicensesService.class); - b.bind(LicensesManagerService.class).to(LicensesService.class); - }); - } - return Collections.emptyList(); + public Collection createComponents(ClusterService clusterService, Clock clock, Environment environment, + ResourceWatcherService resourceWatcherService, + XPackLicenseState licenseState) { + LicenseService licenseService = new LicenseService(settings, clusterService, clock, + environment, resourceWatcherService, licenseState); + return Arrays.asList(licenseService, licenseState); } public List> getSettings() { diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/LicensingClient.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensingClient.java similarity index 62% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/LicensingClient.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensingClient.java index 203f1fb3e0d..6c4aa551211 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/LicensingClient.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicensingClient.java @@ -3,23 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse; -import org.elasticsearch.license.plugin.action.get.GetLicenseAction; -import org.elasticsearch.license.plugin.action.get.GetLicenseRequest; -import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.get.GetLicenseResponse; -import org.elasticsearch.license.plugin.action.put.PutLicenseAction; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; /** * diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseAction.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseAction.java index c4122dfa58d..4aee591b9c5 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseAction.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.put; +package org.elasticsearch.license; import org.elasticsearch.action.Action; import org.elasticsearch.client.ElasticsearchClient; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseRequest.java similarity index 97% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseRequest.java index 7780908cce9..b63358d5358 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseRequest.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.put; +package org.elasticsearch.license; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ValidateActions; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseRequestBuilder.java similarity index 96% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseRequestBuilder.java index 5759cc18f18..d0aae11d113 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseRequestBuilder.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.put; +package org.elasticsearch.license; import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseResponse.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseResponse.java similarity index 97% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseResponse.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseResponse.java index ea0870924d4..967bf9ef1be 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseResponse.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/PutLicenseResponse.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.put; +package org.elasticsearch.license; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.common.io.stream.StreamInput; @@ -11,7 +11,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.license.plugin.core.LicensesStatus; +import org.elasticsearch.license.LicensesStatus; import java.io.IOException; import java.util.Collections; diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestDeleteLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestDeleteLicenseAction.java new file mode 100644 index 00000000000..1244f5b1264 --- /dev/null +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestDeleteLicenseAction.java @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestChannel; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.support.AcknowledgedRestListener; +import org.elasticsearch.xpack.XPackClient; +import org.elasticsearch.xpack.rest.XPackRestHandler; + +import static org.elasticsearch.rest.RestRequest.Method.DELETE; + +public class RestDeleteLicenseAction extends XPackRestHandler { + + @Inject + public RestDeleteLicenseAction(Settings settings, RestController controller) { + super(settings); + // @deprecated Remove deprecations in 6.0 + controller.registerWithDeprecatedHandler(DELETE, URI_BASE + "/license", this, + DELETE, "/_license", deprecationLogger); + + // Remove _licenses support entirely in 6.0 + controller.registerAsDeprecatedHandler(DELETE, "/_licenses", this, + "[DELETE /_licenses] is deprecated! Use " + + "[DELETE /_xpack/license] instead.", + deprecationLogger); + } + + @Override + public void handleRequest(final RestRequest request, final RestChannel channel, final XPackClient client) { + client.es().admin().cluster().execute(DeleteLicenseAction.INSTANCE, + new DeleteLicenseRequest(), + new AcknowledgedRestListener<>(channel)); + } +} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java similarity index 76% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java index f9232382dd8..6c8ace79353 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java @@ -3,24 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.rest; +package org.elasticsearch.license; -import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.action.get.GetLicenseAction; -import org.elasticsearch.license.plugin.action.get.GetLicenseRequest; -import org.elasticsearch.license.plugin.action.get.GetLicenseResponse; -import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.action.support.RestBuilderListener; +import org.elasticsearch.xpack.XPackClient; +import org.elasticsearch.xpack.rest.XPackRestHandler; import java.util.HashMap; import java.util.Map; @@ -29,12 +26,20 @@ import static org.elasticsearch.rest.RestRequest.Method.GET; import static org.elasticsearch.rest.RestStatus.NOT_FOUND; import static org.elasticsearch.rest.RestStatus.OK; -public class RestGetLicenseAction extends BaseRestHandler { +public class RestGetLicenseAction extends XPackRestHandler { @Inject public RestGetLicenseAction(Settings settings, RestController controller) { super(settings); - controller.registerHandler(GET, "/_xpack/license", this); + // @deprecated Remove deprecations in 6.0 + controller.registerWithDeprecatedHandler(GET, URI_BASE + "/license", this, + GET, "/_license", deprecationLogger); + + // Remove _licenses support entirely in 6.0 + controller.registerAsDeprecatedHandler(GET, "/_licenses", this, + "[GET /_licenses] is deprecated! Use " + + "[GET /_xpack/license] instead.", + deprecationLogger); } /** @@ -44,14 +49,14 @@ public class RestGetLicenseAction extends BaseRestHandler { * The licenses are sorted by latest issue_date */ @Override - public void handleRequest(final RestRequest request, final RestChannel channel, final NodeClient client) { + public void handleRequest(final RestRequest request, final RestChannel channel, final XPackClient client) { final Map overrideParams = new HashMap<>(2); overrideParams.put(License.REST_VIEW_MODE, "true"); overrideParams.put(License.LICENSE_VERSION_MODE, String.valueOf(License.VERSION_CURRENT)); final ToXContent.Params params = new ToXContent.DelegatingMapParams(overrideParams, request); GetLicenseRequest getLicenseRequest = new GetLicenseRequest(); getLicenseRequest.local(request.paramAsBoolean("local", getLicenseRequest.local())); - client.admin().cluster().execute(GetLicenseAction.INSTANCE, getLicenseRequest, + client.es().admin().cluster().execute(GetLicenseAction.INSTANCE, getLicenseRequest, new RestBuilderListener(channel) { @Override public RestResponse buildResponse(GetLicenseResponse response, XContentBuilder builder) throws Exception { diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java similarity index 57% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java index 80156b71497..a0ccece2ed9 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestPutLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java @@ -3,17 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.rest; +package org.elasticsearch.license; -import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.license.plugin.action.put.PutLicenseAction; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; -import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestController; @@ -21,25 +16,40 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.action.support.RestBuilderListener; +import org.elasticsearch.xpack.XPackClient; +import org.elasticsearch.xpack.rest.XPackRestHandler; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestRequest.Method.PUT; -public class RestPutLicenseAction extends BaseRestHandler { +public class RestPutLicenseAction extends XPackRestHandler { @Inject public RestPutLicenseAction(Settings settings, RestController controller) { super(settings); - controller.registerHandler(PUT, "/_xpack/license", this); - controller.registerHandler(POST, "/_xpack/license", this); + // @deprecated Remove deprecations in 6.0 + controller.registerWithDeprecatedHandler(POST, URI_BASE + "/license", this, + POST, "/_license", deprecationLogger); + controller.registerWithDeprecatedHandler(PUT, URI_BASE + "/license", this, + PUT, "/_license", deprecationLogger); + + // Remove _licenses support entirely in 6.0 + controller.registerAsDeprecatedHandler(POST, "/_licenses", this, + "[POST /_licenses] is deprecated! Use " + + "[POST /_xpack/license] instead.", + deprecationLogger); + controller.registerAsDeprecatedHandler(PUT, "/_licenses", this, + "[PUT /_licenses] is deprecated! Use " + + "[PUT /_xpack/license] instead.", + deprecationLogger); } @Override - public void handleRequest(final RestRequest request, final RestChannel channel, final NodeClient client) { + public void handleRequest(final RestRequest request, final RestChannel channel, final XPackClient client) { PutLicenseRequest putLicenseRequest = new PutLicenseRequest(); putLicenseRequest.license(request.content().utf8ToString()); putLicenseRequest.acknowledge(request.paramAsBoolean("acknowledge", false)); - client.admin().cluster().execute(PutLicenseAction.INSTANCE, putLicenseRequest, + client.es().admin().cluster().execute(PutLicenseAction.INSTANCE, putLicenseRequest, new RestBuilderListener(channel) { @Override public RestResponse buildResponse(PutLicenseResponse response, XContentBuilder builder) throws Exception { diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportDeleteLicenseAction.java similarity index 85% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportDeleteLicenseAction.java index f36a6b3f5e4..a1d57684a17 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/delete/TransportDeleteLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportDeleteLicenseAction.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.delete; +package org.elasticsearch.license; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; @@ -17,21 +17,20 @@ import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; public class TransportDeleteLicenseAction extends TransportMasterNodeAction { - private final LicensesService licensesService; + private final LicenseService licenseService; @Inject public TransportDeleteLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, - LicensesService licensesService, ThreadPool threadPool, ActionFilters actionFilters, + LicenseService licenseService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { super(settings, DeleteLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, DeleteLicenseRequest::new); - this.licensesService = licensesService; + this.licenseService = licenseService; } @Override @@ -52,7 +51,7 @@ public class TransportDeleteLicenseAction extends TransportMasterNodeAction listener) throws ElasticsearchException { - licensesService.removeLicense(request, new ActionListener() { + licenseService.removeLicense(request, new ActionListener() { @Override public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) { listener.onResponse(new DeleteLicenseResponse(clusterStateUpdateResponse.isAcknowledged())); diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportGetLicenseAction.java similarity index 81% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportGetLicenseAction.java index 5028e9eb51e..c5085b54765 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/get/TransportGetLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportGetLicenseAction.java @@ -3,34 +3,33 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.get; +package org.elasticsearch.license; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeReadAction; -import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicensesManagerService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; public class TransportGetLicenseAction extends TransportMasterNodeReadAction { - private final LicensesManagerService licensesManagerService; + private final LicenseService licenseService; @Inject public TransportGetLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, - LicensesManagerService licensesManagerService, ThreadPool threadPool, ActionFilters actionFilters, + LicenseService licenseService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { super(settings, GetLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, GetLicenseRequest::new); - this.licensesManagerService = licensesManagerService; + this.licenseService = licenseService; } @Override @@ -51,6 +50,6 @@ public class TransportGetLicenseAction extends TransportMasterNodeReadAction listener) throws ElasticsearchException { - listener.onResponse(new GetLicenseResponse(licensesManagerService.getLicense())); + listener.onResponse(new GetLicenseResponse(licenseService.getLicense())); } } diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportPutLicenseAction.java similarity index 84% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportPutLicenseAction.java index d527be2cf0c..9cc2101e501 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/action/put/TransportPutLicenseAction.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TransportPutLicenseAction.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.action.put; +package org.elasticsearch.license; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; @@ -16,21 +16,20 @@ import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; public class TransportPutLicenseAction extends TransportMasterNodeAction { - private final LicensesService licensesService; + private final LicenseService licenseService; @Inject public TransportPutLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, - LicensesService licensesService, ThreadPool threadPool, ActionFilters actionFilters, + LicenseService licenseService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { super(settings, PutLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, PutLicenseRequest::new); - this.licensesService = licensesService; + this.licenseService = licenseService; } @Override @@ -51,7 +50,7 @@ public class TransportPutLicenseAction extends TransportMasterNodeAction listener) throws ElasticsearchException { - licensesService.registerLicense(request, listener); + licenseService.registerLicense(request, listener); } } diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/TrialLicense.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TrialLicense.java similarity index 97% rename from elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/TrialLicense.java rename to elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TrialLicense.java index 7f71d81d885..b816a71bb32 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/TrialLicense.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/TrialLicense.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.ToXContent; @@ -21,7 +21,7 @@ import java.util.Collections; import static org.elasticsearch.license.core.CryptUtils.decrypt; import static org.elasticsearch.license.core.CryptUtils.encrypt; -public class TrialLicense { +class TrialLicense { public static License create(License.Builder specBuilder) { License spec = specBuilder diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/package-info.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/package-info.java new file mode 100644 index 00000000000..4565f23630b --- /dev/null +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * Licensing for xpack. + * + * A {@link org.elasticsearch.license.core.License} is a signed set of json properties that determine what features + * are available in a running cluster. Licenses are registered through a + * {@link org.elasticsearch.license.PutLicenseRequest}. This action is handled by the master node, which places + * the signed license into the cluster state. Each node listens for cluster state updates via the + * {@link org.elasticsearch.license.LicenseService}, and updates its local copy of the license when it detects + * changes in the cluster state. + * + * The logic for which features are available given the current license is handled by + * {@link org.elasticsearch.license.XPackLicenseState}, which is updated by the + * {@link org.elasticsearch.license.LicenseService} when the license changes. + */ +package org.elasticsearch.license; \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/AbstractLicenseeComponent.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/AbstractLicenseeComponent.java deleted file mode 100644 index 73c62e780e5..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/AbstractLicenseeComponent.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.common.component.AbstractLifecycleComponent; -import org.elasticsearch.common.settings.Settings; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * A supporting base class for injectable Licensee components. - */ -public abstract class AbstractLicenseeComponent> extends AbstractLifecycleComponent - implements Licensee { - - private final String id; - private final LicenseeRegistry clientService; - private final List listeners = new CopyOnWriteArrayList<>(); - - // we initialize the licensee state to enabled with trial operation mode - protected volatile Status status = Status.ENABLED; - - protected AbstractLicenseeComponent(Settings settings, String id, LicenseeRegistry clientService) { - super(settings); - this.id = id; - this.clientService = clientService; - } - - @Override - protected void doStart() { - clientService.register(this); - } - - @Override - protected void doStop() { - } - - @Override - protected void doClose() { - } - - @Override - public final String id() { - return id; - } - - /** - * @return the current status of this licensee (can never be null) - */ - public Status getStatus() { - return status; - } - - public void add(Listener listener) { - listeners.add(listener); - } - - @Override - public void onChange(Status status) { - this.status = status; - logger.trace("[{}] is running in [{}] mode", id(), status); - for (Listener listener : listeners) { - listener.onChange(status); - } - } - - public interface Listener { - void onChange(Status status); - } - -} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseSchedule.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseSchedule.java deleted file mode 100644 index 133ea78220f..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseSchedule.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.license.core.License; -import org.elasticsearch.xpack.scheduler.SchedulerEngine; - -import static org.elasticsearch.license.plugin.core.LicensesService.GRACE_PERIOD_DURATION; -import static org.elasticsearch.license.plugin.core.LicensesService.getLicenseState; - -public class LicenseSchedule implements SchedulerEngine.Schedule { - - private final License license; - - LicenseSchedule(License license) { - this.license = license; - } - - @Override - public long nextScheduledTimeAfter(long startTime, long time) { - long nextScheduledTime = -1; - switch (getLicenseState(license, time)) { - case ENABLED: - nextScheduledTime = license.expiryDate(); - break; - case GRACE_PERIOD: - nextScheduledTime = license.expiryDate() + GRACE_PERIOD_DURATION.getMillis(); - break; - case DISABLED: - if (license.issueDate() > time) { - // when we encounter a license with a future issue date - // which can happen with autogenerated license, - // we want to schedule a notification on the license issue date - // so the license is notificed once it is valid - // see https://github.com/elastic/x-plugins/issues/983 - nextScheduledTime = license.issueDate(); - } - break; - } - return nextScheduledTime; - } -} \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseState.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseState.java deleted file mode 100644 index a6eba86c532..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseState.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -/** - * States of a registered licensee - * based on the current license - */ -public enum LicenseState { - - /** - * Active license is valid. - * - * When license expires - * changes to {@link #GRACE_PERIOD} - */ - ENABLED, - - /** - * Active license expired - * but grace period has not. - * - * When grace period expires - * changes to {@link #DISABLED}. - * When valid license is installed - * changes back to {@link #ENABLED} - */ - GRACE_PERIOD, - - /** - * Grace period for active license - * expired. - * - * When a valid license is installed - * changes to {@link #ENABLED}, otherwise - * remains unchanged - */ - DISABLED -} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/Licensee.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/Licensee.java deleted file mode 100644 index cadeb922568..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/Licensee.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.core.License.OperationMode; - -import java.util.Locale; - -public interface Licensee { - - /** - * Unique id used to log expiry and - * acknowledgment messages - */ - String id(); - - /** - * Messages to be printed when - * logging license expiry warnings - */ - String[] expirationMessages(); - - /** - * Messages to be returned when - * installing newLicense - * when currentLicense is - * active - */ - String[] acknowledgmentMessages(License currentLicense, License newLicense); - - /** - * Notifies when a new license is activated - * or when a license state change has occurred - */ - void onChange(Status status); - - /** - * {@code Status} represents both the type and state of a license. - *

- * Most places in the code are expected to use {@code volatile} {@code Status} fields. It's important to follow use a local reference - * whenever checking different parts of the {@code Status}: - *

-     * Status status = this.status;
-     * return status.getLicenseState() != LicenseState.DISABLED &&
-     *        (status.getMode() == OperationMode.TRAIL || status.getMode == OperationMode.PLATINUM);
-     * 
- * Otherwise the license has the potential to change in-between both checks. - */ - class Status { - - public static Status ENABLED = new Status(OperationMode.TRIAL, LicenseState.ENABLED); - public static Status MISSING = new Status(OperationMode.MISSING, LicenseState.DISABLED); - - private final OperationMode mode; - private final LicenseState licenseState; - - public Status(OperationMode mode, LicenseState licenseState) { - this.mode = mode; - this.licenseState = licenseState; - } - - /** - * Returns the operation mode of the license - * responsible for the current licenseState - *

- * Note: Knowing the mode does not indicate whether the {@link #getLicenseState() state} is disabled. If that matters (e.g., - * disabling services when a license becomes disabled), then you should check it as well! - */ - public OperationMode getMode() { - return mode; - } - - /** - * When a license is active, the state is - * {@link LicenseState#ENABLED}, upon license expiry - * the state changes to {@link LicenseState#GRACE_PERIOD} - * and after the grace period has ended the state changes - * to {@link LicenseState#DISABLED} - */ - public LicenseState getLicenseState() { - return licenseState; - } - - @Override - public String toString() { - switch (licenseState) { - case DISABLED: - return "disabled " + mode.name().toLowerCase(Locale.ROOT); - case GRACE_PERIOD: - return mode.name().toLowerCase(Locale.ROOT) + " grace period"; - default: - return mode.name().toLowerCase(Locale.ROOT); - } - } - } -} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseeRegistry.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseeRegistry.java deleted file mode 100644 index 53a855fa637..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicenseeRegistry.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -public interface LicenseeRegistry { - - /** - * Registers a licensee for license notifications - */ - void register(Licensee licensee); -} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java deleted file mode 100644 index 786f3dd970a..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.license.core.License; - -import java.util.List; - -public interface LicensesManagerService { - - /** - * @return current {@link LicenseState} - */ - LicenseState licenseState(); - - /** - * @return the currently active license, or {@code null} if no license is currently installed - */ - License getLicense(); -} diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestDeleteLicenseAction.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestDeleteLicenseAction.java deleted file mode 100644 index 0a2bbd8c2d5..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/plugin/rest/RestDeleteLicenseAction.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.rest; - -import org.elasticsearch.client.node.NodeClient; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; -import org.elasticsearch.rest.BaseRestHandler; -import org.elasticsearch.rest.RestChannel; -import org.elasticsearch.rest.RestController; -import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.rest.action.support.AcknowledgedRestListener; - -import static org.elasticsearch.rest.RestRequest.Method.DELETE; - -public class RestDeleteLicenseAction extends BaseRestHandler { - - @Inject - public RestDeleteLicenseAction(Settings settings, RestController controller) { - super(settings); - controller.registerHandler(DELETE, "/_xpack/license", this); - } - - @Override - public void handleRequest(final RestRequest request, final RestChannel channel, final NodeClient client) { - client.admin().cluster().execute(DeleteLicenseAction.INSTANCE, new DeleteLicenseRequest(), new AcknowledgedRestListener<>(channel)); - } -} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/AbstractLicenseServiceTestCase.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java similarity index 75% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/AbstractLicenseServiceTestCase.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java index 9f4c228aef6..9b712926cf5 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/AbstractLicenseServiceTestCase.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.Version; import org.elasticsearch.cluster.ClusterName; @@ -16,11 +16,16 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.component.Lifecycle; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.LocalTransportAddress; +import org.elasticsearch.env.Environment; import org.elasticsearch.license.core.License; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.support.clock.ClockMock; +import org.junit.After; import org.junit.Before; +import java.nio.file.Path; + import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static org.mockito.Mockito.mock; @@ -28,20 +33,27 @@ import static org.mockito.Mockito.when; public abstract class AbstractLicenseServiceTestCase extends ESTestCase { - protected LicensesService licensesService; + protected LicenseService licenseService; protected ClusterService clusterService; + protected ResourceWatcherService resourceWatcherService; protected ClockMock clock; protected DiscoveryNodes discoveryNodes; + protected Environment environment; @Before public void init() throws Exception { clusterService = mock(ClusterService.class); clock = new ClockMock(); - licensesService = new LicensesService(Settings.EMPTY, clusterService, clock); discoveryNodes = mock(DiscoveryNodes.class); + resourceWatcherService = mock(ResourceWatcherService.class); + environment = mock(Environment.class); } - protected void setInitialState(License license) { + protected void setInitialState(License license, XPackLicenseState licenseState) { + Path tempDir = createTempDir(); + when(environment.configFile()).thenReturn(tempDir); + licenseService = new LicenseService(Settings.EMPTY, clusterService, clock, environment, + resourceWatcherService, licenseState); ClusterState state = mock(ClusterState.class); final ClusterBlocks noBlock = ClusterBlocks.builder().build(); when(state.blocks()).thenReturn(noBlock); @@ -57,4 +69,9 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase { when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED); when(clusterService.getClusterName()).thenReturn(new ClusterName("a")); } -} \ No newline at end of file + + @After + public void after() { + licenseService.stop(); + } +} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTestCase.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/AbstractLicensesIntegrationTestCase.java similarity index 97% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTestCase.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/AbstractLicensesIntegrationTestCase.java index b725ea31189..a6ed13c31ac 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTestCase.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/AbstractLicensesIntegrationTestCase.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; @@ -12,7 +12,7 @@ import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.core.LicensesMetaData; +import org.elasticsearch.license.LicensesMetaData; import org.elasticsearch.xpack.monitoring.Monitoring; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.xpack.security.Security; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/ExpirationCallbackTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/ExpirationCallbackTests.java similarity index 99% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/ExpirationCallbackTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/ExpirationCallbackTests.java index 0e5bc9c46a5..383a82612b8 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/ExpirationCallbackTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/ExpirationCallbackTests.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.license.core.License; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseClusterChangeTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java similarity index 76% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseClusterChangeTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java index 6a0c1168ecd..c3f2c789165 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseClusterChangeTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.Version; import org.elasticsearch.cluster.ClusterChangedEvent; @@ -16,7 +16,6 @@ import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.TestUtils; import org.junit.After; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -31,19 +30,18 @@ import static org.mockito.Mockito.when; public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase { - private TestUtils.AssertingLicensee licensee; + private TestUtils.AssertingLicenseState licenseState; @Before public void setup() { - setInitialState(null); - licensesService.start(); - licensee = new TestUtils.AssertingLicensee("LicenseClusterChangeTests", logger); - licensesService.register(licensee); + licenseState = new TestUtils.AssertingLicenseState(); + setInitialState(null, licenseState); + licenseService.start(); } @After public void teardown() { - licensesService.stop(); + licenseService.stop(); } @@ -52,9 +50,9 @@ public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase { final License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24)); MetaData metaData = MetaData.builder().putCustom(LicensesMetaData.TYPE, new LicensesMetaData(license)).build(); ClusterState newState = ClusterState.builder(new ClusterName("a")).metaData(metaData).build(); - licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); - assertThat(licensee.statuses.size(), equalTo(1)); - assertTrue(licensee.statuses.get(0).getLicenseState() == LicenseState.ENABLED); + licenseService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); + assertThat(licenseState.activeUpdates.size(), equalTo(1)); + assertTrue(licenseState.activeUpdates.get(0)); } public void testNoNotificationOnExistingLicense() throws Exception { @@ -62,8 +60,8 @@ public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase { MetaData metaData = MetaData.builder().putCustom(LicensesMetaData.TYPE, new LicensesMetaData(license)).build(); ClusterState newState = ClusterState.builder(new ClusterName("a")).metaData(metaData).build(); ClusterState oldState = ClusterState.builder(newState).build(); - licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); - assertThat(licensee.statuses.size(), equalTo(0)); + licenseService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); + assertThat(licenseState.activeUpdates.size(), equalTo(0)); } public void testTrialLicenseGeneration() throws Exception { @@ -73,13 +71,13 @@ public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase { when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(true); ClusterState newState = ClusterState.builder(oldState).nodes(discoveryNodes).build(); - licensesService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); + licenseService.clusterChanged(new ClusterChangedEvent("simulated", newState, oldState)); ArgumentCaptor stateUpdater = ArgumentCaptor.forClass(ClusterStateUpdateTask.class); verify(clusterService, times(1)).submitStateUpdateTask(any(), stateUpdater.capture()); ClusterState stateWithLicense = stateUpdater.getValue().execute(newState); LicensesMetaData licenseMetaData = stateWithLicense.metaData().custom(LicensesMetaData.TYPE); assertNotNull(licenseMetaData); assertNotNull(licenseMetaData.getLicense()); - assertEquals(clock.millis() + LicensesService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate()); + assertEquals(clock.millis() + LicenseService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate()); } } \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseRegistrationTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java similarity index 53% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseRegistrationTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java index 131bcb27269..fc2b3f47edc 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseRegistrationTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java @@ -3,17 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateUpdateTask; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.license.plugin.TestUtils; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -21,13 +18,10 @@ import static org.mockito.Mockito.when; public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase { public void testTrialLicenseRequestOnEmptyLicenseState() throws Exception { - setInitialState(null); + XPackLicenseState licenseState = new XPackLicenseState(); + setInitialState(null, licenseState); when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(true); - - TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee( - "testTrialLicenseRequestOnEmptyLicenseState", logger); - licensesService.start(); - licensesService.register(licensee); + licenseService.start(); ClusterState state = ClusterState.builder(new ClusterName("a")).build(); ArgumentCaptor stateUpdater = ArgumentCaptor.forClass(ClusterStateUpdateTask.class); @@ -36,20 +30,6 @@ public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase { LicensesMetaData licenseMetaData = stateWithLicense.metaData().custom(LicensesMetaData.TYPE); assertNotNull(licenseMetaData); assertNotNull(licenseMetaData.getLicense()); - assertEquals(clock.millis() + LicensesService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate()); - - licensesService.stop(); - } - - public void testNotificationOnRegistration() throws Exception { - setInitialState(TestUtils.generateSignedLicense(TimeValue.timeValueHours(2))); - TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee( - "testNotificationOnRegistration", logger); - licensesService.start(); - licensesService.register(licensee); - assertThat(licensee.statuses.size(), equalTo(1)); - final LicenseState licenseState = licensee.statuses.get(0).getLicenseState(); - assertTrue(licenseState == LicenseState.ENABLED); - licensesService.stop(); + assertEquals(clock.millis() + LicenseService.TRIAL_LICENSE_DURATION.millis(), licenseMetaData.getLicense().expiryDate()); } } \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseScheduleTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseScheduleTests.java similarity index 78% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseScheduleTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseScheduleTests.java index a8a179bb463..e6a1a269a22 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseScheduleTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseScheduleTests.java @@ -3,12 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.TestUtils; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.scheduler.SchedulerEngine; import org.junit.Before; import static org.hamcrest.Matchers.equalTo; @@ -16,12 +16,12 @@ import static org.hamcrest.Matchers.equalTo; public class LicenseScheduleTests extends ESTestCase { private License license; - private LicenseSchedule schedule; + private SchedulerEngine.Schedule schedule; @Before public void setuo() throws Exception { license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(12)); - schedule = new LicenseSchedule(license); + schedule = LicenseService.nextLicenseCheck(license); } public void testEnabledLicenseSchedule() throws Exception { @@ -32,13 +32,13 @@ public class LicenseScheduleTests extends ESTestCase { public void testGraceLicenseSchedule() throws Exception { long triggeredTime = license.expiryDate() + between(1, - ((int) LicensesService.GRACE_PERIOD_DURATION.getMillis())); + ((int) LicenseService.GRACE_PERIOD_DURATION.getMillis())); assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime), - equalTo(license.expiryDate() + LicensesService.GRACE_PERIOD_DURATION.getMillis())); + equalTo(license.expiryDate() + LicenseService.GRACE_PERIOD_DURATION.getMillis())); } public void testExpiredLicenseSchedule() throws Exception { - long triggeredTime = license.expiryDate() + LicensesService.GRACE_PERIOD_DURATION.getMillis() + + long triggeredTime = license.expiryDate() + LicenseService.GRACE_PERIOD_DURATION.getMillis() + randomIntBetween(1, 1000); assertThat(schedule.nextScheduledTimeAfter(license.issueDate(), triggeredTime), equalTo(-1L)); @@ -49,4 +49,4 @@ public class LicenseScheduleTests extends ESTestCase { assertThat(schedule.nextScheduledTimeAfter(triggeredTime, triggeredTime), equalTo(license.issueDate())); } -} \ No newline at end of file +} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java new file mode 100644 index 00000000000..cc7eb26c2b8 --- /dev/null +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseServiceClusterTests.java @@ -0,0 +1,201 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import org.elasticsearch.common.network.NetworkModule; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.env.Environment; +import org.elasticsearch.license.core.License; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.test.ESIntegTestCase.ClusterScope; +import org.elasticsearch.xpack.MockNetty3Plugin; +import org.elasticsearch.xpack.XPackPlugin; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; + +import static org.elasticsearch.license.TestUtils.generateSignedLicense; +import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; + +@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0, transportClientRatio = 0) +public class LicenseServiceClusterTests extends AbstractLicensesIntegrationTestCase { + + @Override + protected Settings transportClientSettings() { + return super.transportClientSettings(); + } + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return nodeSettingsBuilder(nodeOrdinal).build(); + } + + private Settings.Builder nodeSettingsBuilder(int nodeOrdinal) { + return Settings.builder() + .put(super.nodeSettings(nodeOrdinal)) + .put("node.data", true) + .put("resource.reload.interval.high", "500ms") // for license mode file watcher + .put(NetworkModule.HTTP_ENABLED.getKey(), true); + } + + @Override + protected Collection> nodePlugins() { + return Arrays.asList(XPackPlugin.class, MockNetty3Plugin.class); + } + + @Override + protected Collection> transportClientPlugins() { + return nodePlugins(); + } + + public void testClusterRestartWithLicense() throws Exception { + wipeAllLicenses(); + + int numNodes = randomIntBetween(1, 5); + logger.info("--> starting {} node(s)", numNodes); + for (int i = 0; i < numNodes; i++) { + internalCluster().startNode(); + } + ensureGreen(); + + logger.info("--> put signed license"); + LicensingClient licensingClient = new LicensingClient(client()); + License license = TestUtils.generateSignedLicense(TimeValue.timeValueMinutes(1)); + putLicense(license); + assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license)); + assertOperationMode(license.operationMode()); + + logger.info("--> restart all nodes"); + internalCluster().fullRestart(); + ensureYellow(); + licensingClient = new LicensingClient(client()); + logger.info("--> get and check signed license"); + assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license)); + logger.info("--> remove licenses"); + licensingClient.prepareDeleteLicense().get(); + assertOperationMode(License.OperationMode.MISSING); + + logger.info("--> restart all nodes"); + internalCluster().fullRestart(); + licensingClient = new LicensingClient(client()); + ensureYellow(); + assertThat(licensingClient.prepareGetLicense().get().license(), nullValue()); + assertOperationMode(License.OperationMode.MISSING); + + + wipeAllLicenses(); + } + + public void testCloudInternalLicense() throws Exception { + wipeAllLicenses(); + + int numNodes = randomIntBetween(1, 5); + logger.info("--> starting {} node(s)", numNodes); + for (int i = 0; i < numNodes; i++) { + internalCluster().startNode(); + } + ensureGreen(); + + logger.info("--> put signed license"); + LicensingClient licensingClient = new LicensingClient(client()); + License license = TestUtils.generateSignedLicense("cloud_internal", License.VERSION_CURRENT, System.currentTimeMillis(), + TimeValue.timeValueMinutes(1)); + putLicense(license); + assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license)); + assertOperationMode(License.OperationMode.PLATINUM); + writeCloudInternalMode("gold"); + assertOperationMode(License.OperationMode.GOLD); + writeCloudInternalMode("basic"); + assertOperationMode(License.OperationMode.BASIC); + } + + public void testClusterRestartWhileEnabled() throws Exception { + wipeAllLicenses(); + internalCluster().startNode(); + ensureGreen(); + assertLicenseActive(true); + logger.info("--> restart node"); + internalCluster().fullRestart(); + ensureYellow(); + logger.info("--> await node for enabled"); + assertLicenseActive(true); + } + + public void testClusterRestartWhileGrace() throws Exception { + wipeAllLicenses(); + internalCluster().startNode(); + assertLicenseActive(true); + putLicense(TestUtils.generateSignedLicense(TimeValue.timeValueMillis(0))); + ensureGreen(); + assertLicenseActive(true); + logger.info("--> restart node"); + internalCluster().fullRestart(); + ensureYellow(); + logger.info("--> await node for grace_period"); + assertLicenseActive(true); + } + + public void testClusterRestartWhileExpired() throws Exception { + wipeAllLicenses(); + internalCluster().startNode(); + ensureGreen(); + assertLicenseActive(true); + putLicense(TestUtils.generateExpiredLicense(System.currentTimeMillis() - LicenseService.GRACE_PERIOD_DURATION.getMillis())); + assertLicenseActive(false); + logger.info("--> restart node"); + internalCluster().fullRestart(); + ensureYellow(); + logger.info("--> await node for disabled"); + assertLicenseActive(false); + } + + public void testClusterNotRecovered() throws Exception { + logger.info("--> start one master out of two [recovery state]"); + internalCluster().startNode(nodeSettingsBuilder(0).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true)); + logger.info("--> start second master out of two [recovered state]"); + internalCluster().startNode(nodeSettingsBuilder(1).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true)); + assertLicenseActive(true); + } + + private void assertLicenseActive(boolean active) throws InterruptedException { + boolean success = awaitBusy(() -> { + for (XPackLicenseState licenseState : internalCluster().getDataNodeInstances(XPackLicenseState.class)) { + if (licenseState.isActive() == active) { + return true; + } + } + return false; + }); + assertTrue(success); + } + + private void assertOperationMode(License.OperationMode operationMode) throws InterruptedException { + boolean success = awaitBusy(() -> { + for (XPackLicenseState licenseState : internalCluster().getDataNodeInstances(XPackLicenseState.class)) { + if (licenseState.getOperationMode() == operationMode) { + return true; + } + } + return false; + }); + assertTrue(success); + } + + private void writeCloudInternalMode(String mode) throws Exception { + for (Environment environment : internalCluster().getDataOrMasterNodeInstances(Environment.class)) { + Path licenseModePath = XPackPlugin.resolveConfigFile(environment, "license_mode"); + Files.createDirectories(licenseModePath.getParent()); + Files.write(licenseModePath, mode.getBytes(StandardCharsets.UTF_8)); + } + } + +} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicenseTribeTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseTribeTests.java similarity index 72% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicenseTribeTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseTribeTests.java index 29d0f7766b5..812214d1cc6 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicenseTribeTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseTribeTests.java @@ -3,19 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.client.Client; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; -import org.elasticsearch.license.plugin.action.get.GetLicenseAction; -import org.elasticsearch.license.plugin.action.get.GetLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseAction; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; -import org.elasticsearch.xpack.TribeTransportTestCase; -import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; +import static org.elasticsearch.license.TestUtils.generateSignedLicense; public class LicenseTribeTests extends TribeTransportTestCase { diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseUtilsTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java similarity index 97% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseUtilsTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java index 73c69b6660b..ab063c0949c 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicenseUtilsTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseUtilsTests.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.test.ESTestCase; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java new file mode 100644 index 00000000000..7d3b365d847 --- /dev/null +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.license.core.License; + +import static org.elasticsearch.license.TestUtils.generateSignedLicense; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase { + + public void testAcknowledgment() throws Exception { + + XPackLicenseState licenseState = new XPackLicenseState(); + setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)), licenseState); + licenseService.start(); + // try installing a signed license + License signedLicense = TestUtils.generateSignedLicense("basic", TimeValue.timeValueHours(10)); + PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense); + // ensure acknowledgement message was part of the response + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID, true)); + assertThat(licenseService.getLicense(), not(signedLicense)); + verify(clusterService, times(0)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + + // try installing a signed license with acknowledgement + putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true); + // ensure license was installed and no acknowledgment message was returned + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID, false)); + verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + } + + private static class AssertingLicensesUpdateResponse implements ActionListener { + private final boolean expectedAcknowledgement; + private final LicensesStatus expectedStatus; + private final boolean expectAckMessages; + + public AssertingLicensesUpdateResponse(boolean expectedAcknowledgement, LicensesStatus expectedStatus, + boolean expectAckMessages) { + this.expectedAcknowledgement = expectedAcknowledgement; + this.expectedStatus = expectedStatus; + this.expectAckMessages = expectAckMessages; + } + + @Override + public void onResponse(PutLicenseResponse licensesUpdateResponse) { + assertThat(licensesUpdateResponse.isAcknowledged(), equalTo(expectedAcknowledgement)); + assertThat(licensesUpdateResponse.status(), equalTo(expectedStatus)); + assertEquals(licensesUpdateResponse.acknowledgeMessages().isEmpty(), expectAckMessages == false); + } + + @Override + public void onFailure(Exception throwable) { + throw new RuntimeException(throwable); + } + } +} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java new file mode 100644 index 00000000000..a22deeccdc5 --- /dev/null +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.license.core.License; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.test.ESSingleNodeTestCase; +import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.graph.Graph; +import org.elasticsearch.xpack.monitoring.Monitoring; +import org.elasticsearch.xpack.security.Security; +import org.elasticsearch.xpack.watcher.Watcher; + +import static org.elasticsearch.license.TestUtils.generateSignedLicense; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class LicensesManagerServiceTests extends ESSingleNodeTestCase { + + @Override + protected Collection> getPlugins() { + return Collections.singletonList(XPackPlugin.class); + } + + @Override + protected Settings nodeSettings() { + return Settings.builder(). + put(XPackPlugin.featureEnabledSetting(Security.NAME), false) + .put(XPackPlugin.featureEnabledSetting(Monitoring.NAME), false) + .put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false) + .put(XPackPlugin.featureEnabledSetting(Graph.NAME), false) + .build(); + } + + @Override + protected boolean resetNodeAfterTest() { + return true; + } + + public void testStoreAndGetLicenses() throws Exception { + LicenseService licenseService = getInstanceFromNode(LicenseService.class); + ClusterService clusterService = getInstanceFromNode(ClusterService.class); + License goldLicense = TestUtils.generateSignedLicense("gold", TimeValue.timeValueHours(1)); + TestUtils.registerAndAckSignedLicenses(licenseService, goldLicense, LicensesStatus.VALID); + License silverLicense = TestUtils.generateSignedLicense("silver", TimeValue.timeValueHours(2)); + TestUtils.registerAndAckSignedLicenses(licenseService, silverLicense, LicensesStatus.VALID); + License platinumLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(1)); + TestUtils.registerAndAckSignedLicenses(licenseService, platinumLicense, LicensesStatus.VALID); + License basicLicense = TestUtils.generateSignedLicense("basic", TimeValue.timeValueHours(3)); + TestUtils.registerAndAckSignedLicenses(licenseService, basicLicense, LicensesStatus.VALID); + LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licensesMetaData.getLicense(), equalTo(basicLicense)); + final License getLicenses = licenseService.getLicense(); + assertThat(getLicenses, equalTo(basicLicense)); + } + + public void testEffectiveLicenses() throws Exception { + final LicenseService licenseService = getInstanceFromNode(LicenseService.class); + final ClusterService clusterService = getInstanceFromNode(ClusterService.class); + License goldLicense = TestUtils.generateSignedLicense("gold", TimeValue.timeValueSeconds(5)); + // put gold license + TestUtils.registerAndAckSignedLicenses(licenseService, goldLicense, LicensesStatus.VALID); + LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licenseService.getLicense(licensesMetaData), equalTo(goldLicense)); + + License platinumLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueSeconds(3)); + // put platinum license + TestUtils.registerAndAckSignedLicenses(licenseService, platinumLicense, LicensesStatus.VALID); + licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licenseService.getLicense(licensesMetaData), equalTo(platinumLicense)); + + License basicLicense = TestUtils.generateSignedLicense("basic", TimeValue.timeValueSeconds(3)); + // put basic license + TestUtils.registerAndAckSignedLicenses(licenseService, basicLicense, LicensesStatus.VALID); + licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licenseService.getLicense(licensesMetaData), equalTo(basicLicense)); + } + + public void testInvalidLicenseStorage() throws Exception { + LicenseService licenseService = getInstanceFromNode(LicenseService.class); + ClusterService clusterService = getInstanceFromNode(ClusterService.class); + License signedLicense = TestUtils.generateSignedLicense(TimeValue.timeValueMinutes(2)); + + // modify content of signed license + License tamperedLicense = License.builder() + .fromLicenseSpec(signedLicense, signedLicense.signature()) + .expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000L) + .validate() + .build(); + + TestUtils.registerAndAckSignedLicenses(licenseService, tamperedLicense, LicensesStatus.INVALID); + + // ensure that the invalid license never made it to cluster state + LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licensesMetaData.getLicense(), not(equalTo(tamperedLicense))); + } + + public void testRemoveLicenses() throws Exception { + LicenseService licenseService = getInstanceFromNode(LicenseService.class); + ClusterService clusterService = getInstanceFromNode(ClusterService.class); + + // generate signed licenses + License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1)); + TestUtils.registerAndAckSignedLicenses(licenseService, license, LicensesStatus.VALID); + LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licensesMetaData.getLicense(), not(LicensesMetaData.LICENSE_TOMBSTONE)); + + // remove signed licenses + removeAndAckSignedLicenses(licenseService); + licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + assertThat(licensesMetaData.getLicense(), equalTo(LicensesMetaData.LICENSE_TOMBSTONE)); + } + + private void removeAndAckSignedLicenses(final LicenseService licenseService) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicBoolean success = new AtomicBoolean(false); + licenseService.removeLicense(new DeleteLicenseRequest(), new ActionListener() { + @Override + public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) { + if (clusterStateUpdateResponse.isAcknowledged()) { + success.set(true); + } + latch.countDown(); + } + + @Override + public void onFailure(Exception throwable) { + latch.countDown(); + } + }); + try { + latch.await(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + assertThat("remove license(s) failed", success.get(), equalTo(true)); + } +} \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesMetaDataSerializationTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesMetaDataSerializationTests.java similarity index 98% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesMetaDataSerializationTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesMetaDataSerializationTests.java index 13337e5e29a..c001cd47daa 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesMetaDataSerializationTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesMetaDataSerializationTests.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin.core; +package org.elasticsearch.license; import org.elasticsearch.Version; import org.elasticsearch.cluster.metadata.MetaData; @@ -21,8 +21,6 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.Licensing; -import org.elasticsearch.license.plugin.TestUtils; import org.elasticsearch.test.ESTestCase; import java.util.Base64; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java similarity index 89% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java index a184dc50570..e471d8df328 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java @@ -3,22 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse; -import org.elasticsearch.license.plugin.action.get.GetLicenseAction; -import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.get.GetLicenseResponse; -import org.elasticsearch.license.plugin.action.put.PutLicenseAction; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; -import org.elasticsearch.license.plugin.core.LicensesStatus; import org.elasticsearch.xpack.monitoring.Monitoring; import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; @@ -30,9 +20,8 @@ import org.elasticsearch.xpack.watcher.Watcher; import java.util.Collection; import java.util.Collections; -import static org.elasticsearch.license.plugin.TestUtils.dateMath; -import static org.elasticsearch.license.plugin.TestUtils.generateExpiredLicense; -import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; +import static org.elasticsearch.license.TestUtils.generateExpiredLicense; +import static org.elasticsearch.license.TestUtils.generateSignedLicense; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/PutLicenseResponseTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/PutLicenseResponseTests.java similarity index 96% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/PutLicenseResponseTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/PutLicenseResponseTests.java index 13d6d8b04fc..a9805f43d0d 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/PutLicenseResponseTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/PutLicenseResponseTests.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; @@ -11,8 +11,6 @@ import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentHelper; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; -import org.elasticsearch.license.plugin.core.LicensesStatus; import org.elasticsearch.test.ESTestCase; import java.io.IOException; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/TestUtils.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java similarity index 65% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/TestUtils.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java index 31ed2b7e3b3..bd6c75430c8 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/TestUtils.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java @@ -3,18 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.client.Client; -import org.elasticsearch.cluster.block.ClusterBlock; -import org.elasticsearch.cluster.block.ClusterBlockLevel; -import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.joda.DateMathParser; import org.elasticsearch.common.joda.FormatDateTimeFormatter; import org.elasticsearch.common.joda.Joda; -import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -22,22 +17,14 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.license.core.License; import org.elasticsearch.license.licensor.LicenseSigner; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicensesService; -import org.elasticsearch.license.plugin.core.LicensesStatus; import org.junit.Assert; -import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static org.elasticsearch.test.ESTestCase.assertNotNull; @@ -92,8 +79,11 @@ public class TestUtils { } public static License generateSignedLicense(String type, long issueDate, TimeValue expiryDuration) throws Exception { + return generateSignedLicense(type, randomIntBetween(License.VERSION_START, License.VERSION_CURRENT), issueDate, expiryDuration); + } + + public static License generateSignedLicense(String type, int version, long issueDate, TimeValue expiryDuration) throws Exception { long issue = (issueDate != -1L) ? issueDate : System.currentTimeMillis() - TimeValue.timeValueHours(2).getMillis(); - int version = randomIntBetween(License.VERSION_START, License.VERSION_CURRENT); final String licenseType; if (version < License.VERSION_NO_FEATURE_TYPE) { licenseType = randomFrom("subscription", "internal", "development"); @@ -117,17 +107,26 @@ public class TestUtils { return signer.sign(builder.build()); } - public static License generateExpiredLicense() throws Exception { - return generateExpiredLicense(System.currentTimeMillis() - TimeValue.timeValueHours(randomIntBetween(1, 10)).getMillis()); + public static License generateExpiredLicense(long expiryDate) throws Exception { + return generateExpiredLicense(randomFrom("basic", "silver", "dev", "gold", "platinum"), expiryDate); } - public static License generateExpiredLicense(long expiryDate) throws Exception { + public static License generateExpiredLicense() throws Exception { + return generateExpiredLicense(randomFrom("basic", "silver", "dev", "gold", "platinum")); + } + + public static License generateExpiredLicense(String type) throws Exception { + return generateExpiredLicense(type, + System.currentTimeMillis() - TimeValue.timeValueHours(randomIntBetween(1, 10)).getMillis()); + } + + public static License generateExpiredLicense(String type, long expiryDate) throws Exception { final License.Builder builder = License.builder() .uid(UUID.randomUUID().toString()) .version(License.VERSION_CURRENT) .expiryDate(expiryDate) .issueDate(expiryDate - TimeValue.timeValueMinutes(10).getMillis()) - .type(randomFrom("basic", "silver", "dev", "gold", "platinum")) + .type(type) .issuedTo("customer") .issuer("elasticsearch") .maxNodes(5); @@ -139,26 +138,12 @@ public class TestUtils { return PathUtils.get(TestUtils.class.getResource(resource).toURI()); } - public static void awaitNoBlock(final Client client) throws InterruptedException { - boolean success = awaitBusy(() -> { - Set clusterBlocks = client.admin().cluster().prepareState().setLocal(true).execute().actionGet() - .getState().blocks().global(ClusterBlockLevel.METADATA_WRITE); - return clusterBlocks.isEmpty(); - }); - assertThat("awaiting no block for too long", success, equalTo(true)); - } - - public static void awaitNoPendingTasks(final Client client) throws InterruptedException { - boolean success = awaitBusy(() -> client.admin().cluster().preparePendingClusterTasks().get().getPendingTasks().isEmpty()); - assertThat("awaiting no pending tasks for too long", success, equalTo(true)); - } - - public static void registerAndAckSignedLicenses(final LicensesService licensesService, License license, + public static void registerAndAckSignedLicenses(final LicenseService licenseService, License license, final LicensesStatus expectedStatus) { - PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(license); + PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(license).acknowledge(true); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference status = new AtomicReference<>(); - licensesService.registerLicense(putLicenseRequest, new ActionListener() { + licenseService.registerLicense(putLicenseRequest, new ActionListener() { @Override public void onResponse(PutLicenseResponse licensesUpdateResponse) { status.set(licensesUpdateResponse.status()); @@ -178,44 +163,14 @@ public class TestUtils { assertThat(status.get(), equalTo(expectedStatus)); } - public static class AssertingLicensee implements Licensee { - public final ESLogger logger; - public final String id; - public final List statuses = new CopyOnWriteArrayList<>(); - public final AtomicInteger expirationMessagesCalled = new AtomicInteger(0); - public final List> acknowledgementRequested = new CopyOnWriteArrayList<>(); - - private String[] acknowledgmentMessages = new String[0]; - - public AssertingLicensee(String id, ESLogger logger) { - this.logger = logger; - this.id = id; - } - - public void setAcknowledgementMessages(String[] acknowledgementMessages) { - this.acknowledgmentMessages = acknowledgementMessages; - } - @Override - public String id() { - return id; - } + public static class AssertingLicenseState extends XPackLicenseState { + public final List modeUpdates = new ArrayList<>(); + public final List activeUpdates = new ArrayList<>(); @Override - public String[] expirationMessages() { - expirationMessagesCalled.incrementAndGet(); - return new String[0]; - } - - @Override - public String[] acknowledgmentMessages(License currentLicense, License newLicense) { - acknowledgementRequested.add(new Tuple<>(currentLicense, newLicense)); - return acknowledgmentMessages; - } - - @Override - public void onChange(Status status) { - assertNotNull(status); - statuses.add(status); + void update(License.OperationMode mode, boolean active) { + modeUpdates.add(mode); + activeUpdates.add(active); } } -} \ No newline at end of file +} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/TrialLicenseTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TrialLicenseTests.java similarity index 98% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/TrialLicenseTests.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TrialLicenseTests.java index 26c34df6483..4b6e127f57d 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/TrialLicenseTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TrialLicenseTests.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.license.plugin; +package org.elasticsearch.license; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.unit.TimeValue; @@ -12,7 +12,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.core.TrialLicense; +import org.elasticsearch.license.TrialLicense; import org.elasticsearch.test.ESTestCase; import java.io.IOException; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/xpack/TribeTransportTestCase.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TribeTransportTestCase.java similarity index 94% rename from elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/xpack/TribeTransportTestCase.java rename to elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TribeTransportTestCase.java index 962ca7f5e37..004e49370ce 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/xpack/TribeTransportTestCase.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TribeTransportTestCase.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack; +package org.elasticsearch.license; import org.elasticsearch.action.Action; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; @@ -19,7 +19,9 @@ import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.discovery.DiscoveryModule; import org.elasticsearch.discovery.zen.ping.unicast.UnicastZenPing; +import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.monitoring.Monitoring; import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; @@ -59,7 +61,8 @@ public abstract class TribeTransportTestCase extends ESIntegTestCase { protected final Settings nodeSettings(int nodeOrdinal) { final Settings.Builder builder = Settings.builder() .put(NetworkModule.HTTP_ENABLED.getKey(), false) - .put(Node.NODE_LOCAL_SETTING.getKey(), true); + .put("transport.type", "local") + .put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), "local"); List enabledFeatures = enabledFeatures(); for (String feature : ALL_FEATURES) { builder.put(XPackPlugin.featureEnabledSetting(feature), enabledFeatures.contains(feature)); @@ -99,7 +102,7 @@ public abstract class TribeTransportTestCase extends ESIntegTestCase { return TribeTransportTestCase.this.transportClientPlugins(); } }; - final InternalTestCluster cluster2 = new InternalTestCluster(InternalTestCluster.configuredNodeMode(), + final InternalTestCluster cluster2 = new InternalTestCluster( randomLong(), createTempDir(), true, 2, 2, UUIDs.randomBase64UUID(random()), nodeConfigurationSource, 1, false, "tribe_node2", getMockPlugins(), getClientWrapper()); @@ -131,12 +134,17 @@ public abstract class TribeTransportTestCase extends ESIntegTestCase { Settings merged = Settings.builder() .put("tribe.t1.cluster.name", internalCluster().getClusterName()) .put("tribe.t2.cluster.name", cluster2.getClusterName()) + .put("tribe.t1.transport.type", "local") + .put("tribe.t2.transport.type", "local") + .put("tribe.t1.discovery.type", "local") + .put("tribe.t2.discovery.type", "local") .put("tribe.blocks.write", false) .put(tribe1Defaults.build()) .put(tribe2Defaults.build()) + .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put(internalCluster().getDefaultSettings()) .put("node.name", "tribe_node") // make sure we can identify threads from this node - .put(Node.NODE_LOCAL_SETTING.getKey(), true) + .put("transport.type", "local") .build(); final Node tribeNode = new Node(merged).start(); diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTests.java deleted file mode 100644 index dff4dc1d955..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTests.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin; - -import org.elasticsearch.client.ClusterAdminClient; -import org.elasticsearch.common.network.NetworkModule; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse; -import org.elasticsearch.license.plugin.action.get.GetLicenseAction; -import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.get.GetLicenseResponse; -import org.elasticsearch.license.plugin.action.put.PutLicenseAction; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.LicensesMetaData; -import org.elasticsearch.license.plugin.core.LicensesService; -import org.elasticsearch.license.plugin.core.LicensesStatus; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.ESIntegTestCase.ClusterScope; -import org.elasticsearch.xpack.XPackPlugin; - -import java.util.Collection; -import java.util.Collections; - -import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; -import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; - -@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0, transportClientRatio = 0) -public class LicensesServiceClusterTests extends AbstractLicensesIntegrationTestCase { - - @Override - protected Settings transportClientSettings() { - return super.transportClientSettings(); - } - - @Override - protected Settings nodeSettings(int nodeOrdinal) { - return nodeSettingsBuilder(nodeOrdinal).build(); - } - - private Settings.Builder nodeSettingsBuilder(int nodeOrdinal) { - return Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .put("node.data", true) - .put(NetworkModule.HTTP_ENABLED.getKey(), true); - } - - @Override - protected Collection> nodePlugins() { - return Collections.singletonList(XPackPlugin.class); - } - - @Override - protected Collection> transportClientPlugins() { - return nodePlugins(); - } - - public void testClusterRestartWithLicense() throws Exception { - wipeAllLicenses(); - - int numNodes = randomIntBetween(1, 5); - logger.info("--> starting {} node(s)", numNodes); - for (int i = 0; i < numNodes; i++) { - internalCluster().startNode(); - } - ensureGreen(); - - logger.info("--> put signed license"); - License license = generateAndPutLicenses(); - getAndCheckLicense(license); - logger.info("--> restart all nodes"); - internalCluster().fullRestart(); - ensureYellow(); - - logger.info("--> get and check signed license"); - getAndCheckLicense(license); - - logger.info("--> remove licenses"); - removeLicense(); - assertNoLicense(); - logger.info("--> restart all nodes"); - internalCluster().fullRestart(); - ensureYellow(); - assertNoLicense(); - - wipeAllLicenses(); - } - - - private void assertLicenseState(LicenseState state) throws InterruptedException { - boolean success = awaitBusy(() -> { - for (LicensesService service : internalCluster().getDataNodeInstances(LicensesService.class)) { - if (service.licenseState() == state) { - return true; - } - } - return false; - }); - assertTrue(success); - } - - public void testClusterRestartWhileEnabled() throws Exception { - wipeAllLicenses(); - internalCluster().startNode(); - ensureGreen(); - assertLicenseState(LicenseState.ENABLED); - logger.info("--> restart node"); - internalCluster().fullRestart(); - ensureYellow(); - logger.info("--> await node for enabled"); - assertLicenseState(LicenseState.ENABLED); - } - - public void testClusterRestartWhileGrace() throws Exception { - wipeAllLicenses(); - internalCluster().startNode(); - assertLicenseState(LicenseState.ENABLED); - putLicense(TestUtils.generateSignedLicense(TimeValue.timeValueMillis(0))); - ensureGreen(); - assertLicenseState(LicenseState.GRACE_PERIOD); - logger.info("--> restart node"); - internalCluster().fullRestart(); - ensureYellow(); - logger.info("--> await node for grace_period"); - assertLicenseState(LicenseState.GRACE_PERIOD); - } - - public void testClusterRestartWhileExpired() throws Exception { - wipeAllLicenses(); - internalCluster().startNode(); - ensureGreen(); - assertLicenseState(LicenseState.ENABLED); - putLicense(TestUtils.generateExpiredLicense(System.currentTimeMillis() - LicensesService.GRACE_PERIOD_DURATION.getMillis())); - assertLicenseState(LicenseState.DISABLED); - logger.info("--> restart node"); - internalCluster().fullRestart(); - ensureYellow(); - logger.info("--> await node for disabled"); - assertLicenseState(LicenseState.DISABLED); - } - - public void testClusterNotRecovered() throws Exception { - logger.info("--> start one master out of two [recovery state]"); - internalCluster().startNode(nodeSettingsBuilder(0).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true)); - logger.info("--> start second master out of two [recovered state]"); - internalCluster().startNode(nodeSettingsBuilder(1).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true)); - assertLicenseState(LicenseState.ENABLED); - } - - private void removeLicense() throws Exception { - ClusterAdminClient cluster = internalCluster().client().admin().cluster(); - ensureGreen(); - License putLicenses = generateSignedLicense(TimeValue.timeValueMinutes(1)); - PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster, PutLicenseAction.INSTANCE); - putLicenseRequestBuilder.setLicense(putLicenses); - DeleteLicenseResponse response = new DeleteLicenseRequestBuilder(cluster, DeleteLicenseAction.INSTANCE).get(); - assertThat(response.isAcknowledged(), equalTo(true)); - } - - private License generateAndPutLicenses() throws Exception { - ClusterAdminClient cluster = internalCluster().client().admin().cluster(); - ensureGreen(); - License putLicenses = generateSignedLicense(TimeValue.timeValueMinutes(1)); - PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster, PutLicenseAction.INSTANCE); - putLicenseRequestBuilder.setLicense(putLicenses); - final PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); - assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); - assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID)); - return putLicenses; - } - - private void assertNoLicense() { - ClusterAdminClient cluster = internalCluster().client().admin().cluster(); - final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster, GetLicenseAction.INSTANCE).get(); - assertThat(response.license(), nullValue()); - LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData, notNullValue()); - assertThat(licensesMetaData.getLicense(), equalTo(LicensesMetaData.LICENSE_TOMBSTONE)); - } - - private void getAndCheckLicense(License license) { - ClusterAdminClient cluster = internalCluster().client().admin().cluster(); - final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster, GetLicenseAction.INSTANCE).get(); - assertThat(response.license(), equalTo(license)); - LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData, notNullValue()); - assertThat(licensesMetaData.getLicense(), not(LicensesMetaData.LICENSE_TOMBSTONE)); - } -} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/AbstractLicenseeTestCase.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/AbstractLicenseeTestCase.java deleted file mode 100644 index b1dc8a942ae..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/AbstractLicenseeTestCase.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.test.ESTestCase; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Provides helper methods for {@link Licensee} implementations. - *

- * Note: This requires that this class be on the classpath for those implementations. - */ -public abstract class AbstractLicenseeTestCase extends ESTestCase { - /** - * Ensure when going from {@code fromMode} to {@code toMode}, nothing gets reported. - *

- * This will randomly {@code null}-out the {@code fromMode} license. - * - * @param fromMode Original / current license - * @param toMode New license - * @param licensee The licensee to test - */ - public static void assertEmptyAck(OperationMode fromMode, OperationMode toMode, Licensee licensee) { - License fromLicense = mock(License.class); - when(fromLicense.operationMode()).thenReturn(fromMode); - License toLicense = mock(License.class); - when(toLicense.operationMode()).thenReturn(toMode); - - if (randomBoolean()) { - fromLicense = null; - } - - // test it - String[] messages = licensee.acknowledgmentMessages(fromLicense, toLicense); - - assertThat(fromToMessage(fromMode, toMode), messages.length, equalTo(0)); - } - - /** - * Ensure when going from {@code fromMode} to {@code toMode}, nothing gets reported. - *

- * This will randomly {@code null}-out the {@code fromMode} license. - * - * @param fromMode Original / current license - * @param toMode New license - * @param licenseeSupplier Supplies the licensee to test - */ - public static void assertEmptyAck(OperationMode fromMode, OperationMode toMode, Supplier licenseeSupplier) { - assertEmptyAck(fromMode, toMode, licenseeSupplier.get()); - } - - /** - * Get the ack when changing {@code fromMode} to {@code toMode}. - *

- * This just serves to remove a lot of duplicated code. - * - * @param fromMode Original / current license - * @param toMode New license - * @param licensee The licensee to test - */ - public static String[] ackLicenseChange(OperationMode fromMode, OperationMode toMode, Licensee licensee) { - License fromLicense = mock(License.class); - when(fromLicense.operationMode()).thenReturn(fromMode); - License toLicense = mock(License.class); - when(toLicense.operationMode()).thenReturn(toMode); - - return licensee.acknowledgmentMessages(fromLicense, toLicense); - } - - /** - * Ensure when going from {@code fromMode} to {@code toMode}, nothing gets reported. - *

- * This just serves to remove a lot of duplicated code. - * - * @param fromMode Original / current license - * @param toMode New license - * @param licenseeSupplier Supplies the licensee to test - */ - public static String[] ackLicenseChange(OperationMode fromMode, OperationMode toMode, Supplier licenseeSupplier) { - return ackLicenseChange(fromMode, toMode, licenseeSupplier.get()); - } - - /** - * Randomly get {@link OperationMode#TRIAL} or {@link OperationMode#PLATINUM}. - * - * @return Never {@code null}. - */ - public static OperationMode randomTrialOrPlatinumMode() { - return randomFrom(OperationMode.TRIAL, OperationMode.PLATINUM); - } - - /** - * Randomly get {@link OperationMode#TRIAL}, {@link OperationMode#STANDARD}, {@link OperationMode#GOLD}, or - * {@link OperationMode#PLATINUM}. - * - * @return Never {@code null}. - */ - public static OperationMode randomTrialStandardGoldOrPlatinumMode() { - return randomFrom(OperationMode.TRIAL, OperationMode.STANDARD, OperationMode.GOLD, OperationMode.PLATINUM); - } - - /** - * Randomly get any {@link OperationMode}. - * - * @return Never {@code null}. - */ - public static OperationMode randomMode() { - return randomFrom(OperationMode.values()); - } - - /** - * Get any {@link #randomMode() mode}, except the selected {@code mode}. - * - * @param mode The mode to exclude. - * @return Never {@code null}. - */ - public static OperationMode randomModeExcept(OperationMode mode) { - return randomValueOtherThan(mode, AbstractLicenseeTestCase::randomMode); - } - - /** - * Randomly get {@link LicenseState#ENABLED} or {@link LicenseState#GRACE_PERIOD}. - * - * @return Never {@code null}. - */ - public static LicenseState randomEnabledOrGracePeriodState() { - return randomFrom(LicenseState.ENABLED, LicenseState.GRACE_PERIOD); - } - - /** - * Get a random value from the {@code values} that passes {@code filter}. - * - * @param values The values to filter and randomly select from - * @param filter The filter to apply - * @return Never {@code null}. - * @throws IllegalArgumentException if nothing matches the {@code filter} - * @see #randomFrom(Object[]) - */ - public static T randomFrom(T[] values, Predicate filter) { - return randomFrom(Arrays.stream(values).filter(filter).collect(Collectors.toList())); - } - - /** - * Get a message to show with assertions for license transition. - * - * @param fromMode Coming "from" mode - * @param toMode Going "to" mode - * @return Never {@code null}. - */ - public static String fromToMessage(OperationMode fromMode, OperationMode toMode) { - return String.format(Locale.ROOT, "From [%s] to [%s]", fromMode, toMode); - } - - public static class SimpleLicenseeRegistry extends AbstractComponent implements LicenseeRegistry { - private final List licensees = new ArrayList<>(); - private OperationMode operationMode; - - public SimpleLicenseeRegistry() { - super(Settings.EMPTY); - } - - @Override - public void register(Licensee licensee) { - licensees.add(licensee); - enable(); - } - - public void enable() { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(operationMode, randomEnabledOrGracePeriodState())); - } - } - - public void disable() { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(operationMode, LicenseState.DISABLED)); - } - } - - public void setOperationMode(License.OperationMode operationMode) { - this.operationMode = operationMode; - enable(); - } - } -} diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesAcknowledgementTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesAcknowledgementTests.java deleted file mode 100644 index 68a896be279..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesAcknowledgementTests.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.cluster.ClusterStateUpdateTask; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.TestUtils; -import org.elasticsearch.license.plugin.action.put.PutLicenseRequest; -import org.elasticsearch.license.plugin.action.put.PutLicenseResponse; - -import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.not; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase { - - public void testAcknowledgment() throws Exception { - setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2))); - licensesService.start(); - String id = "testAcknowledgment"; - String[] acknowledgeMessages = new String[] {"message"}; - TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee(id, logger); - licensee.setAcknowledgementMessages(acknowledgeMessages); - licensesService.register(licensee); - // try installing a signed license - License signedLicense = generateSignedLicense(TimeValue.timeValueHours(10)); - PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense); - // ensure acknowledgement message was part of the response - licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID, - Collections.singletonMap(id, acknowledgeMessages))); - assertThat(licensee.acknowledgementRequested.size(), equalTo(1)); - assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense)); - assertThat(licensesService.getLicense(), not(signedLicense)); - - // try installing a signed license with acknowledgement - putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true); - // ensure license was installed and no acknowledgment message was returned - licensee.setAcknowledgementMessages(new String[0]); - licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID, - Collections.emptyMap())); - verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); - assertThat(licensee.acknowledgementRequested.size(), equalTo(1)); - assertThat(licensee.acknowledgementRequested.get(0).v2(), equalTo(signedLicense)); - licensesService.stop(); - } - - public void testAcknowledgementMultipleLicensee() throws Exception { - setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2))); - licensesService.start(); - String id1 = "testAcknowledgementMultipleLicensee_1"; - String[] acknowledgeMessages1 = new String[] {"testAcknowledgementMultipleLicensee_1"}; - String id2 = "testAcknowledgementMultipleLicensee_2"; - String[] acknowledgeMessages2 = new String[] {"testAcknowledgementMultipleLicensee_2"}; - TestUtils.AssertingLicensee licensee1 = new TestUtils.AssertingLicensee(id1, logger); - licensee1.setAcknowledgementMessages(acknowledgeMessages1); - TestUtils.AssertingLicensee licensee2 = new TestUtils.AssertingLicensee(id2, logger); - licensee2.setAcknowledgementMessages(acknowledgeMessages2); - licensesService.register(licensee1); - licensesService.register(licensee2); - // try installing a signed license - License signedLicense = generateSignedLicense(TimeValue.timeValueHours(10)); - PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense); - // ensure acknowledgement message was part of the response - final HashMap expectedMessages = new HashMap<>(); - expectedMessages.put(id1, acknowledgeMessages1); - expectedMessages.put(id2, acknowledgeMessages2); - licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID, - expectedMessages)); - verify(clusterService, times(0)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); - assertThat(licensee2.acknowledgementRequested.size(), equalTo(1)); - assertThat(licensee2.acknowledgementRequested.get(0).v2(), equalTo(signedLicense)); - assertThat(licensee1.acknowledgementRequested.size(), equalTo(1)); - assertThat(licensee1.acknowledgementRequested.get(0).v2(), equalTo(signedLicense)); - assertThat(licensesService.getLicense(), not(signedLicense)); - - // try installing a signed license with acknowledgement - putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true); - // ensure license was installed and no acknowledgment message was returned - licensee1.setAcknowledgementMessages(new String[0]); - licensee2.setAcknowledgementMessages(new String[0]); - licensesService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID, - Collections.emptyMap())); - verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); - licensesService.stop(); - } - - private static class AssertingLicensesUpdateResponse implements ActionListener { - private final boolean expectedAcknowledgement; - private final LicensesStatus expectedStatus; - private final Map expectedAckMessages; - - public AssertingLicensesUpdateResponse(boolean expectedAcknowledgement, LicensesStatus expectedStatus, - Map expectedAckMessages) { - this.expectedAcknowledgement = expectedAcknowledgement; - this.expectedStatus = expectedStatus; - this.expectedAckMessages = expectedAckMessages; - } - - @Override - public void onResponse(PutLicenseResponse licensesUpdateResponse) { - assertThat(licensesUpdateResponse.isAcknowledged(), equalTo(expectedAcknowledgement)); - assertThat(licensesUpdateResponse.status(), equalTo(expectedStatus)); - assertThat(licensesUpdateResponse.acknowledgeMessages().size(), equalTo(expectedAckMessages.size())); - for (Map.Entry expectedEntry : expectedAckMessages.entrySet()) { - Map actual = licensesUpdateResponse.acknowledgeMessages(); - assertThat(actual.containsKey(expectedEntry.getKey()), equalTo(true)); - String[] actualMessages = actual.get(expectedEntry.getKey()); - assertThat(actualMessages, equalTo(expectedEntry.getValue())); - } - } - - @Override - public void onFailure(Exception throwable) { - } - } -} \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesManagerServiceTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesManagerServiceTests.java deleted file mode 100644 index b7d8f75afa2..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesManagerServiceTests.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; -import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.xpack.graph.Graph; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.TestUtils; -import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest; -import org.elasticsearch.xpack.monitoring.Monitoring; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.test.ESSingleNodeTestCase; -import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.watcher.Watcher; - -import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -public class LicensesManagerServiceTests extends ESSingleNodeTestCase { - - @Override - protected Collection> getPlugins() { - return Collections.singletonList(XPackPlugin.class); - } - - @Override - protected Settings nodeSettings() { - return Settings.builder(). - put(XPackPlugin.featureEnabledSetting(Security.NAME), false) - .put(XPackPlugin.featureEnabledSetting(Monitoring.NAME), false) - .put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false) - .put(XPackPlugin.featureEnabledSetting(Graph.NAME), false) - .build(); - } - - @Override - protected boolean resetNodeAfterTest() { - return true; - } - - public void testStoreAndGetLicenses() throws Exception { - LicensesService licensesService = getInstanceFromNode(LicensesService.class); - ClusterService clusterService = getInstanceFromNode(ClusterService.class); - License goldLicense = generateSignedLicense("gold", TimeValue.timeValueHours(1)); - TestUtils.registerAndAckSignedLicenses(licensesService, goldLicense, LicensesStatus.VALID); - License silverLicense = generateSignedLicense("silver", TimeValue.timeValueHours(2)); - TestUtils.registerAndAckSignedLicenses(licensesService, silverLicense, LicensesStatus.VALID); - License platinumLicense = generateSignedLicense("platinum", TimeValue.timeValueHours(1)); - TestUtils.registerAndAckSignedLicenses(licensesService, platinumLicense, LicensesStatus.VALID); - License basicLicense = generateSignedLicense("basic", TimeValue.timeValueHours(3)); - TestUtils.registerAndAckSignedLicenses(licensesService, basicLicense, LicensesStatus.VALID); - LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData.getLicense(), equalTo(basicLicense)); - final License getLicenses = licensesService.getLicense(); - assertThat(getLicenses, equalTo(basicLicense)); - } - - public void testEffectiveLicenses() throws Exception { - final LicensesService licensesService = getInstanceFromNode(LicensesService.class); - final ClusterService clusterService = getInstanceFromNode(ClusterService.class); - License goldLicense = generateSignedLicense("gold", TimeValue.timeValueSeconds(5)); - // put gold license - TestUtils.registerAndAckSignedLicenses(licensesService, goldLicense, LicensesStatus.VALID); - LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesService.getLicense(licensesMetaData), equalTo(goldLicense)); - - License platinumLicense = generateSignedLicense("platinum", TimeValue.timeValueSeconds(3)); - // put platinum license - TestUtils.registerAndAckSignedLicenses(licensesService, platinumLicense, LicensesStatus.VALID); - licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesService.getLicense(licensesMetaData), equalTo(platinumLicense)); - - License basicLicense = generateSignedLicense("basic", TimeValue.timeValueSeconds(3)); - // put basic license - TestUtils.registerAndAckSignedLicenses(licensesService, basicLicense, LicensesStatus.VALID); - licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesService.getLicense(licensesMetaData), equalTo(basicLicense)); - } - - public void testInvalidLicenseStorage() throws Exception { - LicensesService licensesService = getInstanceFromNode(LicensesService.class); - ClusterService clusterService = getInstanceFromNode(ClusterService.class); - License signedLicense = generateSignedLicense(TimeValue.timeValueMinutes(2)); - - // modify content of signed license - License tamperedLicense = License.builder() - .fromLicenseSpec(signedLicense, signedLicense.signature()) - .expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000L) - .validate() - .build(); - - TestUtils.registerAndAckSignedLicenses(licensesService, tamperedLicense, LicensesStatus.INVALID); - - // ensure that the invalid license never made it to cluster state - LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData.getLicense(), not(equalTo(tamperedLicense))); - } - - public void testRemoveLicenses() throws Exception { - LicensesService licensesService = getInstanceFromNode(LicensesService.class); - ClusterService clusterService = getInstanceFromNode(ClusterService.class); - - // generate a trial license for one feature - licensesService.register(new TestUtils.AssertingLicensee("", logger)); - - // generate signed licenses - License license = generateSignedLicense(TimeValue.timeValueHours(1)); - TestUtils.registerAndAckSignedLicenses(licensesService, license, LicensesStatus.VALID); - LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData.getLicense(), not(LicensesMetaData.LICENSE_TOMBSTONE)); - - // remove signed licenses - removeAndAckSignedLicenses(licensesService); - licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData.getLicense(), equalTo(LicensesMetaData.LICENSE_TOMBSTONE)); - } - - public void testRemoveLicensesAndLicenseeNotification() throws Exception { - LicensesService licensesService = getInstanceFromNode(LicensesService.class); - licensesService.start(); - ClusterService clusterService = getInstanceFromNode(ClusterService.class); - - // generate a trial license for one feature - TestUtils.AssertingLicensee licensee = new TestUtils.AssertingLicensee("", logger); - licensesService.register(licensee); - - // we should get a trial license to begin with - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(licensee.statuses, hasSize(1)); - assertThat(licensee.statuses.get(0).getMode(), is(License.OperationMode.TRIAL)); - assertThat(licensee.statuses.get(0).getLicenseState(), is(LicenseState.ENABLED)); - } - }); - - - // generate signed licenses - License license = generateSignedLicense("gold", TimeValue.timeValueHours(1)); - TestUtils.registerAndAckSignedLicenses(licensesService, license, LicensesStatus.VALID); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(licensee.statuses, hasSize(2)); - assertThat(licensee.statuses.get(1).getMode(), not(License.OperationMode.TRIAL)); - assertThat(licensee.statuses.get(1).getLicenseState(), is(LicenseState.ENABLED)); - } - }); - - // remove signed licenses - removeAndAckSignedLicenses(licensesService); - assertBusy(new Runnable() { - @Override - public void run() { - assertThat(licensee.statuses, hasSize(3)); - } - }); - LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); - assertThat(licensesMetaData.getLicense(), is(LicensesMetaData.LICENSE_TOMBSTONE)); - assertThat(licensee.statuses, hasSize(3)); - assertThat(licensee.statuses.get(2).getLicenseState(), is(LicenseState.DISABLED)); - assertThat(licensee.statuses.get(2).getMode(), is(License.OperationMode.MISSING)); - } - - private void removeAndAckSignedLicenses(final LicensesService licensesService) { - final CountDownLatch latch = new CountDownLatch(1); - final AtomicBoolean success = new AtomicBoolean(false); - licensesService.removeLicense(new DeleteLicenseRequest(), new ActionListener() { - @Override - public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) { - if (clusterStateUpdateResponse.isAcknowledged()) { - success.set(true); - } - latch.countDown(); - } - - @Override - public void onFailure(Exception throwable) { - latch.countDown(); - } - }); - try { - latch.await(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - assertThat("remove license(s) failed", success.get(), equalTo(true)); - } -} \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesNotificationTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesNotificationTests.java deleted file mode 100644 index a6824ad5867..00000000000 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/plugin/core/LicensesNotificationTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license.plugin.core; - -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.TestUtils; -import org.elasticsearch.license.plugin.TestUtils.AssertingLicensee; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; - -import java.util.List; - -import static org.hamcrest.Matchers.equalTo; - -public class LicensesNotificationTests extends AbstractLicenseServiceTestCase { - - public void testLicenseNotification() throws Exception { - final License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(48)); - setInitialState(license); - licensesService.start(); - int nLicensee = randomIntBetween(1, 3); - AssertingLicensee[] assertingLicensees = new AssertingLicensee[nLicensee]; - for (int i = 0; i < assertingLicensees.length; i++) { - assertingLicensees[i] = new AssertingLicensee("testLicenseNotification" + i, logger); - licensesService.register(assertingLicensees[i]); - assertLicenseStates(assertingLicensees[i], LicenseState.ENABLED); - } - clock.fastForward(TimeValue.timeValueMillis(license.expiryDate() - clock.millis())); - final LicensesMetaData licensesMetaData = new LicensesMetaData(license); - licensesService.onUpdate(licensesMetaData); - for (AssertingLicensee assertingLicensee : assertingLicensees) { - assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD); - } - clock.fastForward(TimeValue.timeValueMillis((license.expiryDate() + - LicensesService.GRACE_PERIOD_DURATION.getMillis()) - clock.millis())); - licensesService.onUpdate(licensesMetaData); - for (AssertingLicensee assertingLicensee : assertingLicensees) { - assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED); - } - clock.setTime(new DateTime(DateTimeZone.UTC)); - final License newLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(2)); - clock.fastForward(TimeValue.timeValueHours(1)); - licensesService.onUpdate(new LicensesMetaData(newLicense)); - for (AssertingLicensee assertingLicensee : assertingLicensees) { - assertLicenseStates(assertingLicensee, LicenseState.ENABLED, LicenseState.GRACE_PERIOD, LicenseState.DISABLED, - LicenseState.ENABLED); - } - licensesService.stop(); - } - - private void assertLicenseStates(AssertingLicensee licensee, LicenseState... states) { - StringBuilder msg = new StringBuilder(); - msg.append("Actual: "); - msg.append(dumpLicensingStates(licensee.statuses)); - msg.append(" Expected: "); - msg.append(dumpLicensingStates(states)); - assertThat(msg.toString(), licensee.statuses.size(), equalTo(states.length)); - for (int i = 0; i < states.length; i++) { - assertThat(msg.toString(), licensee.statuses.get(i).getLicenseState(), equalTo(states[i])); - } - } - - private String dumpLicensingStates(List statuses) { - return dumpLicensingStates(statuses.toArray(new Licensee.Status[statuses.size()])); - } - - private String dumpLicensingStates(Licensee.Status... statuses) { - LicenseState[] states = new LicenseState[statuses.length]; - for (int i = 0; i < statuses.length; i++) { - states[i] = statuses[i].getLicenseState(); - } - return dumpLicensingStates(states); - } - - private String dumpLicensingStates(LicenseState... states) { - StringBuilder sb = new StringBuilder(); - sb.append("["); - for (int i = 0; i < states.length; i++) { - sb.append(states[i].name()); - if (i != states.length - 1) { - sb.append(", "); - } - } - sb.append("]"); - return sb.toString(); - } -} \ No newline at end of file diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/Monitoring.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/Monitoring.java index e32e347ad11..bb359f5453c 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/Monitoring.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/Monitoring.java @@ -78,9 +78,7 @@ public class Monitoring implements ActionPlugin { if (enabled == false || transportClientMode || tribeNode) { return Collections.emptyList(); } - return Arrays.>asList(MonitoringLicensee.class, - AgentService.class, - CleanerService.class); + return Arrays.>asList(AgentService.class, CleanerService.class); } @Override diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSet.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSet.java index cef76ffd35a..5f5bb70edaf 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSet.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSet.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.XPackFeatureSet; import org.elasticsearch.xpack.monitoring.agent.exporter.Exporter; import org.elasticsearch.xpack.monitoring.agent.exporter.Exporters; @@ -26,14 +27,14 @@ import java.util.Map; public class MonitoringFeatureSet implements XPackFeatureSet { private final boolean enabled; - private final MonitoringLicensee licensee; + private final XPackLicenseState licenseState; private final Exporters exporters; @Inject - public MonitoringFeatureSet(Settings settings, @Nullable MonitoringLicensee licensee, @Nullable Exporters exporters, + public MonitoringFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable Exporters exporters, NamedWriteableRegistry namedWriteableRegistry) { this.enabled = MonitoringSettings.ENABLED.get(settings); - this.licensee = licensee; + this.licenseState = licenseState; this.exporters = exporters; namedWriteableRegistry.register(Usage.class, Usage.writeableName(Monitoring.NAME), Usage::new); } @@ -50,7 +51,7 @@ public class MonitoringFeatureSet implements XPackFeatureSet { @Override public boolean available() { - return licensee != null && licensee.isAvailable(); + return licenseState != null && licenseState.isMonitoringAllowed(); } @Override diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringLicensee.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringLicensee.java deleted file mode 100644 index 058bff4c18c..00000000000 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringLicensee.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.monitoring; - -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.logging.LoggerMessageFormat; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; - -/** - * {@code MonitoringLicensee} determines whether certain features of Monitoring are enabled or disabled. - *

- * Once the license expires, the agent will stop: - *

    - *
  • Collecting and publishing new metrics.
  • - *
  • Cleaning up (deleting) older indices.
  • - *
- */ -public class MonitoringLicensee extends AbstractLicenseeComponent implements Licensee { - - @Inject - public MonitoringLicensee(Settings settings, LicenseeRegistry clientService) { - super(settings, Monitoring.NAME, clientService); - } - - /** - * {@inheritDoc} - * - * @see #collectionEnabled() - * @see #cleaningEnabled() - */ - @Override - public String[] expirationMessages() { - return new String[] { - "The agent will stop collecting cluster and indices metrics", - "The agent will stop automatically cleaning indices older than [xpack.monitoring.history.duration]", - }; - } - - @Override - public String[] acknowledgmentMessages(License currentLicense, License newLicense) { - switch (newLicense.operationMode()) { - case BASIC: - if (currentLicense != null) { - switch (currentLicense.operationMode()) { - case TRIAL: - case STANDARD: - case GOLD: - case PLATINUM: - return new String[] { - LoggerMessageFormat.format( - "Multi-cluster support is disabled for clusters with [{}] license. If you are\n" + - "running multiple clusters, users won't be able to access the clusters with\n" + - "[{}] licenses from within a single X-Pack Kibana instance. You will have to deploy a\n" + - "separate and dedicated X-pack Kibana instance for each [{}] cluster you wish to monitor.", - newLicense.type(), newLicense.type(), newLicense.type()), - LoggerMessageFormat.format( - "Automatic index cleanup is locked to {} days for clusters with [{}] license.", - MonitoringSettings.HISTORY_DURATION.getDefault(Settings.EMPTY).days(), newLicense.type()) - }; - } - } - break; - } - return Strings.EMPTY_ARRAY; - } - - /** - * Monitoring is always available as long as there is a valid license - * - * @return true - */ - public boolean isAvailable() { - return status.getLicenseState() != LicenseState.DISABLED; - } - - /** - * Determine if the index cleaning service is enabled. - *

- * Collection is only disabled automatically when the license expires. All modes are valid for collection. - *

- * Collection can be disabled explicitly by the user, although that's generally a temporary solution to unrelated issues - * (e.g., initial setup when the monitoring cluster doesn't actually exist). - * - * @return {@code true} as long as the license is valid. Otherwise {@code false}. - */ - public boolean collectionEnabled() { - return status.getLicenseState() != LicenseState.DISABLED; - } - - /** - * Determine if the index cleaning service is enabled. - *

- * Index cleaning is only disabled when the license expires. All modes are valid for cleaning. - * - * @return {@code true} as long as the license is valid. Otherwise {@code false}. - */ - public boolean cleaningEnabled() { - return status.getLicenseState() != LicenseState.DISABLED; - } - - /** - * Determine if the current license allows the retention of indices to be modified. - *

- * Only users with a non-{@link OperationMode#BASIC} license can update the retention period. - *

- * Note: This does not consider the state of the license so that any change is remembered for when they fix their license. - * - * @return {@code true} if the user is allowed to modify the retention. Otherwise {@code false}. - */ - public boolean allowUpdateRetention() { - final OperationMode mode = status.getMode(); - return mode != OperationMode.BASIC && mode != OperationMode.MISSING; - } -} diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringModule.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringModule.java index 7234cfa8fe1..6c7908b82c3 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringModule.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/MonitoringModule.java @@ -26,12 +26,9 @@ public class MonitoringModule extends AbstractModule { XPackPlugin.bindFeatureSet(binder(), MonitoringFeatureSet.class); if (enabled && transportClientMode == false) { - bind(MonitoringLicensee.class).asEagerSingleton(); bind(MonitoringSettings.class).asEagerSingleton(); bind(AgentService.class).asEagerSingleton(); bind(CleanerService.class).asEagerSingleton(); - } else { - bind(MonitoringLicensee.class).toProvider(Providers.of(null)); } } } diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollector.java index 2d8ee861298..700a9c35f0f 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollector.java @@ -12,8 +12,8 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -25,16 +25,16 @@ public abstract class AbstractCollector extends AbstractLifecycleComponent imple protected final ClusterService clusterService; protected final MonitoringSettings monitoringSettings; - protected final MonitoringLicensee licensee; + protected final XPackLicenseState licenseState; @Inject public AbstractCollector(Settings settings, String name, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee) { + MonitoringSettings monitoringSettings, XPackLicenseState licenseState) { super(settings); this.name = name; this.clusterService = clusterService; this.monitoringSettings = monitoringSettings; - this.licensee = licensee; + this.licenseState = licenseState; } @Override @@ -61,7 +61,7 @@ public abstract class AbstractCollector extends AbstractLifecycleComponent imple * Indicates if the current collector is allowed to collect data */ protected boolean shouldCollect() { - if (!licensee.collectionEnabled()) { + if (licenseState.isMonitoringAllowed() == false) { logger.trace("collector [{}] can not collect data due to invalid license", name()); return false; } diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollector.java index 8c3db642714..f68f747f434 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollector.java @@ -13,7 +13,7 @@ import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -38,8 +38,8 @@ public class ClusterStateCollector extends AbstractCollector { @Inject public ClusterStateCollector(Settings settings, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee, InternalClient client) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + MonitoringSettings monitoringSettings, XPackLicenseState licenseState, InternalClient client) { + super(settings, NAME, clusterService, monitoringSettings, licenseState); this.client = client; } diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollector.java index efcdf64b0bb..70eae5a2c36 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollector.java @@ -13,9 +13,9 @@ import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicenseUtils; -import org.elasticsearch.license.plugin.core.LicensesManagerService; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.LicenseService; +import org.elasticsearch.license.LicenseUtils; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -40,16 +40,16 @@ public class ClusterStatsCollector extends AbstractCollector { public static final String NAME = "cluster-stats-collector"; - private final LicensesManagerService licensesManagerService; + private final LicenseService licenseService; private final Client client; @Inject public ClusterStatsCollector(Settings settings, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee, InternalClient client, - LicensesManagerService licensesManagerService) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + MonitoringSettings monitoringSettings, XPackLicenseState licenseState, InternalClient client, + LicenseService licenseService) { + super(settings, NAME, clusterService, monitoringSettings, licenseState); this.client = client; - this.licensesManagerService = licensesManagerService; + this.licenseService = licenseService; } @Override @@ -85,7 +85,7 @@ public class ClusterStatsCollector extends AbstractCollector { clusterInfoDoc.setSourceNode(sourceNode); clusterInfoDoc.setClusterName(clusterService.getClusterName().value()); clusterInfoDoc.setVersion(Version.CURRENT.toString()); - clusterInfoDoc.setLicense(licensesManagerService.getLicense()); + clusterInfoDoc.setLicense(licenseService.getLicense()); clusterInfoDoc.setClusterStats(clusterStats); results.add(clusterInfoDoc); diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollector.java index fcf9fd65e66..0daff80330e 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollector.java @@ -13,7 +13,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -40,8 +40,8 @@ public class IndexRecoveryCollector extends AbstractCollector { @Inject public IndexRecoveryCollector(Settings settings, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee, InternalClient client) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + MonitoringSettings monitoringSettings, XPackLicenseState licenseState, InternalClient client) { + super(settings, NAME, clusterService, monitoringSettings, licenseState); this.client = client; } diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollector.java index f52c45d0ed6..ed9f83ccda7 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollector.java @@ -15,7 +15,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -42,8 +42,8 @@ public class IndexStatsCollector extends AbstractCollector { @Inject public IndexStatsCollector(Settings settings, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee, InternalClient client) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + MonitoringSettings monitoringSettings, XPackLicenseState licenseState, InternalClient client) { + super(settings, NAME, clusterService, monitoringSettings, licenseState); this.client = client; } @@ -68,6 +68,8 @@ public class IndexStatsCollector extends AbstractCollector { .setSegments(true) .setStore(true) .setRefresh(true) + .setQueryCache(true) + .setRequestCache(true) .get(monitoringSettings.indexStatsTimeout()); long timestamp = System.currentTimeMillis(); diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollector.java index 2f6c538ea20..973a48d961b 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollector.java @@ -13,7 +13,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -37,8 +37,8 @@ public class IndicesStatsCollector extends AbstractCollector { @Inject public IndicesStatsCollector(Settings settings, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee, InternalClient client) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + MonitoringSettings monitoringSettings, XPackLicenseState licenseState, InternalClient client) { + super(settings, NAME, clusterService, monitoringSettings, licenseState); this.client = client; } diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollector.java index cbbeefb0836..ada9e1edfc2 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollector.java @@ -17,7 +17,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.NodeEnvironment; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -43,9 +43,9 @@ public class NodeStatsCollector extends AbstractCollector { @Inject public NodeStatsCollector(Settings settings, ClusterService clusterService, MonitoringSettings monitoringSettings, - MonitoringLicensee licensee, InternalClient client, + XPackLicenseState licenseState, InternalClient client, NodeEnvironment nodeEnvironment, DiskThresholdDecider diskThresholdDecider) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + super(settings, NAME, clusterService, monitoringSettings, licenseState); this.client = client; this.nodeEnvironment = nodeEnvironment; this.diskThresholdDecider = diskThresholdDecider; diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollector.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollector.java index 58ffd7097ac..748c01797b2 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollector.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollector.java @@ -13,7 +13,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -36,8 +36,8 @@ public class ShardsCollector extends AbstractCollector { @Inject public ShardsCollector(Settings settings, ClusterService clusterService, - MonitoringSettings monitoringSettings, MonitoringLicensee licensee) { - super(settings, NAME, clusterService, monitoringSettings, licensee); + MonitoringSettings monitoringSettings, XPackLicenseState licenseState) { + super(settings, NAME, clusterService, monitoringSettings, licenseState); } @Override diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolver.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolver.java index 9878ce1f0ee..d412aefae62 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolver.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolver.java @@ -26,12 +26,22 @@ public class IndexStatsResolver extends MonitoringIndexNameResolver.Timestamped< "index_stats.index", "index_stats.primaries.docs.count", "index_stats.primaries.fielddata.memory_size_in_bytes", + "index_stats.primaries.fielddata.evictions", "index_stats.primaries.indexing.index_total", "index_stats.primaries.indexing.index_time_in_millis", "index_stats.primaries.indexing.throttle_time_in_millis", "index_stats.primaries.merges.total_size_in_bytes", + "index_stats.primaries.query_cache.memory_size_in_bytes", + "index_stats.primaries.query_cache.evictions", + "index_stats.primaries.query_cache.hit_count", + "index_stats.primaries.query_cache.miss_count", + "index_stats.primaries.request_cache.memory_size_in_bytes", + "index_stats.primaries.request_cache.evictions", + "index_stats.primaries.request_cache.hit_count", + "index_stats.primaries.request_cache.miss_count", "index_stats.primaries.search.query_total", "index_stats.primaries.search.query_time_in_millis", + "index_stats.primaries.segments.count", "index_stats.primaries.segments.memory_in_bytes", "index_stats.primaries.segments.terms_memory_in_bytes", "index_stats.primaries.segments.stored_fields_memory_in_bytes", @@ -47,12 +57,22 @@ public class IndexStatsResolver extends MonitoringIndexNameResolver.Timestamped< "index_stats.primaries.refresh.total_time_in_millis", "index_stats.total.docs.count", "index_stats.total.fielddata.memory_size_in_bytes", + "index_stats.total.fielddata.evictions", "index_stats.total.indexing.index_total", "index_stats.total.indexing.index_time_in_millis", "index_stats.total.indexing.throttle_time_in_millis", "index_stats.total.merges.total_size_in_bytes", + "index_stats.total.query_cache.memory_size_in_bytes", + "index_stats.total.query_cache.evictions", + "index_stats.total.query_cache.hit_count", + "index_stats.total.query_cache.miss_count", + "index_stats.total.request_cache.memory_size_in_bytes", + "index_stats.total.request_cache.evictions", + "index_stats.total.request_cache.hit_count", + "index_stats.total.request_cache.miss_count", "index_stats.total.search.query_total", "index_stats.total.search.query_time_in_millis", + "index_stats.total.segments.count", "index_stats.total.segments.memory_in_bytes", "index_stats.total.segments.terms_memory_in_bytes", "index_stats.total.segments.stored_fields_memory_in_bytes", diff --git a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolver.java b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolver.java index 7003c5f8b1a..948ca7a9ff9 100644 --- a/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolver.java +++ b/elasticsearch/x-pack/monitoring/src/main/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolver.java @@ -32,11 +32,21 @@ public class NodeStatsResolver extends MonitoringIndexNameResolver.Timestamped listeners = new CopyOnWriteArrayList<>(); @@ -36,10 +36,10 @@ public class CleanerService extends AbstractLifecycleComponent { private volatile TimeValue globalRetention; - CleanerService(Settings settings, ClusterSettings clusterSettings, MonitoringLicensee licensee, ThreadPool threadPool, + CleanerService(Settings settings, ClusterSettings clusterSettings, XPackLicenseState licenseState, ThreadPool threadPool, ExecutionScheduler executionScheduler) { super(settings); - this.licensee = licensee; + this.licenseState = licenseState; this.threadPool = threadPool; this.executionScheduler = executionScheduler; this.globalRetention = MonitoringSettings.HISTORY_DURATION.get(settings); @@ -50,8 +50,8 @@ public class CleanerService extends AbstractLifecycleComponent { } @Inject - public CleanerService(Settings settings, ClusterSettings clusterSettings, ThreadPool threadPool, MonitoringLicensee licensee) { - this(settings, clusterSettings, licensee,threadPool, new DefaultExecutionScheduler()); + public CleanerService(Settings settings, ClusterSettings clusterSettings, ThreadPool threadPool, XPackLicenseState licenseState) { + this(settings, clusterSettings, licenseState, threadPool, new DefaultExecutionScheduler()); } @Override @@ -85,11 +85,11 @@ public class CleanerService extends AbstractLifecycleComponent { * This will ignore the global retention if the license does not allow retention updates. * * @return Never {@code null} - * @see MonitoringLicensee#allowUpdateRetention() + * @see XPackLicenseState#isUpdateRetentionAllowed() */ public TimeValue getRetention() { // we only care about their value if they are allowed to set it - if (licensee.allowUpdateRetention() && globalRetention != null) { + if (licenseState.isUpdateRetentionAllowed() && globalRetention != null) { return globalRetention; } else { @@ -107,7 +107,7 @@ public class CleanerService extends AbstractLifecycleComponent { */ public void setGlobalRetention(TimeValue globalRetention) { // notify the user that their setting will be ignored until they get the right license - if (licensee.allowUpdateRetention() == false) { + if (licenseState.isUpdateRetentionAllowed() == false) { logger.warn("[{}] setting will be ignored until an appropriate license is applied", MonitoringSettings.HISTORY_DURATION.getKey()); } @@ -165,7 +165,7 @@ public class CleanerService extends AbstractLifecycleComponent { @Override protected void doRunInLifecycle() throws Exception { - if (licensee.cleaningEnabled() == false) { + if (licenseState.isMonitoringAllowed() == false) { logger.debug("cleaning service is disabled due to invalid license"); return; } diff --git a/elasticsearch/x-pack/monitoring/src/main/resources/monitoring-es.json b/elasticsearch/x-pack/monitoring/src/main/resources/monitoring-es.json index b9dc8f22d67..3bbad2a8adc 100644 --- a/elasticsearch/x-pack/monitoring/src/main/resources/monitoring-es.json +++ b/elasticsearch/x-pack/monitoring/src/main/resources/monitoring-es.json @@ -109,6 +109,9 @@ "properties": { "memory_size_in_bytes": { "type": "long" + }, + "evictions": { + "type": "long" } } }, @@ -142,6 +145,38 @@ } } }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, "search": { "properties": { "query_total": { @@ -154,6 +189,9 @@ }, "segments": { "properties": { + "count": { + "type": "integer" + }, "memory_in_bytes": { "type": "long" }, @@ -205,6 +243,9 @@ "properties": { "memory_size_in_bytes": { "type": "long" + }, + "evictions": { + "type": "long" } } }, @@ -238,6 +279,38 @@ } } }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, "search": { "properties": { "query_total": { @@ -250,6 +323,9 @@ }, "segments": { "properties": { + "count": { + "type": "integer" + }, "memory_in_bytes": { "type": "long" }, @@ -374,6 +450,16 @@ } } }, + "fielddata" : { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + } + } + }, "indexing": { "properties": { "index_time_in_millis": { @@ -387,6 +473,38 @@ } } }, + "query_cache": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, + "request_cache": { + "properties": { + "memory_size_in_bytes": { + "type": "long" + }, + "evictions": { + "type": "long" + }, + "hit_count": { + "type": "long" + }, + "miss_count": { + "type": "long" + } + } + }, "search": { "properties": { "query_time_in_millis": { @@ -400,7 +518,7 @@ "segments": { "properties": { "count": { - "type": "long" + "type": "integer" }, "memory_in_bytes": { "type": "long" diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSetTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSetTests.java index 457ddd951a5..492bbaee09c 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSetTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringFeatureSetTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.monitoring; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.XPackFeatureSet; import org.elasticsearch.xpack.monitoring.agent.exporter.Exporter; @@ -33,26 +34,26 @@ import static org.mockito.Mockito.when; */ public class MonitoringFeatureSetTests extends ESTestCase { - private MonitoringLicensee licensee; + private XPackLicenseState licenseState; private NamedWriteableRegistry namedWriteableRegistry; private Exporters exporters; @Before public void init() throws Exception { - licensee = mock(MonitoringLicensee.class); + licenseState = mock(XPackLicenseState.class); exporters = mock(Exporters.class); namedWriteableRegistry = mock(NamedWriteableRegistry.class); } public void testWritableRegistration() throws Exception { - new MonitoringFeatureSet(Settings.EMPTY, licensee, exporters, namedWriteableRegistry); + new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry); verify(namedWriteableRegistry).register(eq(MonitoringFeatureSet.Usage.class), eq("xpack.usage.monitoring"), anyObject()); } public void testAvailable() throws Exception { - MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licensee, exporters, namedWriteableRegistry); + MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry); boolean available = randomBoolean(); - when(licensee.isAvailable()).thenReturn(available); + when(licenseState.isMonitoringAllowed()).thenReturn(available); assertThat(featureSet.available(), is(available)); } @@ -60,12 +61,12 @@ public class MonitoringFeatureSetTests extends ESTestCase { boolean enabled = randomBoolean(); Settings.Builder settings = Settings.builder(); settings.put("xpack.monitoring.enabled", enabled); - MonitoringFeatureSet featureSet = new MonitoringFeatureSet(settings.build(), licensee, exporters, namedWriteableRegistry); + MonitoringFeatureSet featureSet = new MonitoringFeatureSet(settings.build(), licenseState, exporters, namedWriteableRegistry); assertThat(featureSet.enabled(), is(enabled)); } public void testEnabledDefault() throws Exception { - MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licensee, exporters, namedWriteableRegistry); + MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry); assertThat(featureSet.enabled(), is(true)); } @@ -102,7 +103,7 @@ public class MonitoringFeatureSetTests extends ESTestCase { } when(exporters.iterator()).thenReturn(exporterList.iterator()); - MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licensee, exporters, namedWriteableRegistry); + MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry); XPackFeatureSet.Usage usage = featureSet.usage(); assertThat(usage.name(), is(featureSet.name())); assertThat(usage.enabled(), is(featureSet.enabled())); diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringTribeTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringTribeTests.java index 1776a73e813..f7be9d3b4a1 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringTribeTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/MonitoringTribeTests.java @@ -7,7 +7,7 @@ package org.elasticsearch.xpack.monitoring; import org.elasticsearch.client.Client; import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.xpack.TribeTransportTestCase; +import org.elasticsearch.license.TribeTransportTestCase; import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.monitoring.action.MonitoringBulkDoc; import org.elasticsearch.xpack.monitoring.action.MonitoringBulkRequest; diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollectorTestCase.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollectorTestCase.java index a88cd9e248f..aa4cdddba20 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollectorTestCase.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/AbstractCollectorTestCase.java @@ -5,68 +5,28 @@ */ package org.elasticsearch.xpack.monitoring.agent.collector; -import com.carrotsearch.randomizedtesting.RandomizedTest; -import com.carrotsearch.randomizedtesting.SysGlobals; -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.cluster.block.ClusterBlocks; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.component.LifecycleComponent; -import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Module; -import org.elasticsearch.common.network.NetworkModule; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.Licensing; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; -import org.elasticsearch.license.plugin.core.LicensesManagerService; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.rest.RestHandler; -import org.elasticsearch.xpack.security.InternalClient; -import org.elasticsearch.test.ESIntegTestCase; -import org.elasticsearch.test.ESIntegTestCase.ClusterScope; -import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.monitoring.MonitoringSettings; -import org.elasticsearch.xpack.monitoring.test.MonitoringIntegTestCase; -import org.junit.Before; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.concurrent.TimeUnit; -import static java.util.Collections.emptyList; -import static org.elasticsearch.common.unit.TimeValue.timeValueMinutes; +import org.elasticsearch.cluster.block.ClusterBlocks; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.ESIntegTestCase.ClusterScope; +import org.elasticsearch.xpack.monitoring.MonitoringSettings; +import org.elasticsearch.xpack.monitoring.test.MonitoringIntegTestCase; +import org.elasticsearch.xpack.security.InternalClient; @ClusterScope(scope = ESIntegTestCase.Scope.SUITE, randomDynamicTemplates = false, transportClientRatio = 0.0) public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase { - @Override - protected Collection> nodePlugins() { - return Arrays.asList(InternalXPackPlugin.class); - } - @Override protected Settings nodeSettings(int nodeOrdinal) { return Settings.builder() .put(super.nodeSettings(nodeOrdinal)) - .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put(MonitoringSettings.INTERVAL.getKey(), "-1") .build(); } - @Before - public void ensureLicenseIsEnabled() { - enableLicense(); - } - public InternalClient securedClient() { return internalCluster().getInstance(InternalClient.class); } @@ -82,83 +42,6 @@ public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase assertNotNull(results); } - protected void assertCannotCollect(AbstractCollector collector) { - assertNotNull(collector); - assertFalse("collector [" + collector.name() + "] should not be able to collect data", collector.shouldCollect()); - Collection results = collector.collect(); - assertTrue(results == null || results.isEmpty()); - } - - private static License createTestingLicense(long issueDate, long expiryDate) { - return License.builder() - .expiryDate(expiryDate) - .issueDate(issueDate) - .issuedTo("AbstractCollectorTestCase") - .issuer("test") - .maxNodes(Integer.MAX_VALUE) - .signature("_signature") - .type("trial") - .uid(String.valueOf(RandomizedTest.systemPropertyAsInt(SysGlobals.CHILDVM_SYSPROP_JVM_ID, 0)) + - System.identityHashCode(AbstractCollectorTestCase.class)) - .build(); - } - - protected static void enableLicense() { - long issueDate = System.currentTimeMillis(); - long expiryDate = issueDate + randomDaysInMillis(); - - final License license = createTestingLicense(issueDate, expiryDate); - for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { - service.onChange(license.operationMode(), LicenseState.ENABLED); - } - for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { - service.update(license); - } - } - - protected static void beginGracefulPeriod() { - long expiryDate = System.currentTimeMillis() + timeValueMinutes(10).millis(); - long issueDate = expiryDate - randomDaysInMillis(); - - final License license = createTestingLicense(issueDate, expiryDate); - for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { - service.onChange(license.operationMode(), LicenseState.GRACE_PERIOD); - } - for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { - service.update(license); - } - } - - protected static void endGracefulPeriod() { - long expiryDate = System.currentTimeMillis() - MonitoringSettings.MAX_LICENSE_GRACE_PERIOD.millis() - timeValueMinutes(10).millis(); - long issueDate = expiryDate - randomDaysInMillis(); - - final License license = createTestingLicense(issueDate, expiryDate); - for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { - service.onChange(license.operationMode(), LicenseState.DISABLED); - } - for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { - service.update(license); - } - } - - protected static void disableLicense() { - long expiryDate = System.currentTimeMillis() - MonitoringSettings.MAX_LICENSE_GRACE_PERIOD.millis() - randomDaysInMillis(); - long issueDate = expiryDate - randomDaysInMillis(); - - final License license = createTestingLicense(issueDate, expiryDate); - for (LicenseServiceForCollectors service : internalCluster().getInstances(LicenseServiceForCollectors.class)) { - service.onChange(license.operationMode(), LicenseState.DISABLED); - } - for (LicensesManagerServiceForCollectors service : internalCluster().getInstances(LicensesManagerServiceForCollectors.class)) { - service.update(license); - } - } - - private static long randomDaysInMillis() { - return TimeValue.timeValueHours(randomIntBetween(1, 30) * 24).millis(); - } - public void waitForNoBlocksOnNodes() throws Exception { assertBusy(new Runnable() { @Override @@ -182,88 +65,4 @@ public abstract class AbstractCollectorTestCase extends MonitoringIntegTestCase assertTrue(clusterBlocks.indices().values().isEmpty()); }, 30L, TimeUnit.SECONDS); } - - public static class InternalLicensing extends Licensing { - - public InternalLicensing() { - super(Settings.EMPTY); - } - - @Override - public Collection nodeModules() { - return Collections.singletonList(new AbstractModule() { - - @Override - protected void configure() { - bind(LicenseServiceForCollectors.class).asEagerSingleton(); - bind(LicenseeRegistry.class).to(LicenseServiceForCollectors.class); - bind(LicensesManagerServiceForCollectors.class).asEagerSingleton(); - bind(LicensesManagerService.class).to(LicensesManagerServiceForCollectors.class); - } - }); - } - - @Override - public List, ? extends ActionResponse>> getActions() { - return emptyList(); - } - - @Override - public List> getRestHandlers() { - return emptyList(); - } - - @Override - public Collection> nodeServices() { - return Collections.emptyList(); - } - } - - public static class InternalXPackPlugin extends XPackPlugin { - - public InternalXPackPlugin(Settings settings) throws IOException { - super(settings); - licensing = new InternalLicensing(); - } - } - - public static class LicenseServiceForCollectors extends AbstractComponent implements LicenseeRegistry { - - private final List licensees = new ArrayList<>(); - - @Inject - public LicenseServiceForCollectors(Settings settings) { - super(settings); - } - - @Override - public void register(Licensee licensee) { - licensees.add(licensee); - } - - public void onChange(License.OperationMode operationMode, LicenseState state) { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(operationMode, state)); - } - } - } - - public static class LicensesManagerServiceForCollectors implements LicensesManagerService { - - private volatile License license; - - @Override - public LicenseState licenseState() { - return null; - } - - @Override - public License getLicense() { - return license; - } - - public synchronized void update(License license) { - this.license = license; - } - } } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollectorTests.java index f7e92724ee5..8eead22ed08 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStateCollectorTests.java @@ -10,8 +10,8 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -106,44 +106,6 @@ public class ClusterStateCollectorTests extends AbstractCollectorTestCase { } } - public void testClusterStateCollectorWithLicensing() { - try { - String[] nodes = internalCluster().getNodeNames(); - for (String node : nodes) { - logger.debug("--> creating a new instance of the collector"); - ClusterStateCollector collector = newClusterStateCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data if node is master"); - enableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> starting graceful period and checks that the collector can still collect data if node is master"); - beginGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> ending graceful period and checks that the collector cannot collect data"); - endGracefulPeriod(); - assertCannotCollect(collector); - - logger.debug("--> disabling license and checks that the collector cannot collect data"); - disableLicense(); - assertCannotCollect(collector); - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - private ClusterStateCollector newClusterStateCollector() { // This collector runs on master node only return newClusterStateCollector(internalCluster().getMasterName()); @@ -154,7 +116,7 @@ public class ClusterStateCollectorTests extends AbstractCollectorTestCase { return new ClusterStateCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId), + internalCluster().getInstance(XPackLicenseState.class, nodeId), securedClient(nodeId)); } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollectorTests.java index a54443e98e2..e026d90b7ba 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/cluster/ClusterStatsCollectorTests.java @@ -5,20 +5,20 @@ */ package org.elasticsearch.xpack.monitoring.agent.collector.cluster; +import java.util.Collection; + import org.apache.lucene.util.LuceneTestCase.BadApple; import org.elasticsearch.Version; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.plugin.core.LicensesManagerService; +import org.elasticsearch.license.LicenseService; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollector; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; -import java.util.Collection; - import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; @@ -75,52 +75,6 @@ public class ClusterStatsCollectorTests extends AbstractCollectorTestCase { equalTo(internalCluster().getNodeNames().length)); } - public void testClusterStatsCollectorWithLicensing() { - try { - String[] nodes = internalCluster().getNodeNames(); - for (String node : nodes) { - logger.debug("--> creating a new instance of the collector"); - ClusterStatsCollector collector = newClusterStatsCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data if node is master"); - enableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector, ClusterInfoMonitoringDoc.class, ClusterStatsMonitoringDoc.class); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> starting graceful period and checks that the collector can still collect data if node is master"); - beginGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector, ClusterInfoMonitoringDoc.class, ClusterStatsMonitoringDoc.class); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> ending graceful period and checks that the collector can still collect data (if node is master)"); - endGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector, ClusterInfoMonitoringDoc.class); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> disabling license and checks that the collector can still collect data (if node is master)"); - disableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector, ClusterInfoMonitoringDoc.class); - } else { - assertCannotCollect(collector); - } - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - private ClusterStatsCollector newClusterStatsCollector() { // This collector runs on master node only return newClusterStatsCollector(internalCluster().getMasterName()); @@ -131,9 +85,9 @@ public class ClusterStatsCollectorTests extends AbstractCollectorTestCase { return new ClusterStatsCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId), + internalCluster().getInstance(XPackLicenseState.class, nodeId), securedClient(nodeId), - internalCluster().getInstance(LicensesManagerService.class, nodeId)); + internalCluster().getInstance(LicenseService.class, nodeId)); } private void assertCanCollect(AbstractCollector collector, Class... classes) { diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollectorTests.java index 86c24fa2b25..e1c59c10222 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexRecoveryCollectorTests.java @@ -13,10 +13,10 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.indices.recovery.RecoveryState; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.hamcrest.ElasticsearchAssertions; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -130,46 +130,6 @@ public class IndexRecoveryCollectorTests extends AbstractCollectorTestCase { } } - public void testIndexRecoveryCollectorWithLicensing() throws Exception { - List nodesIds = internalCluster().startNodesAsync(randomIntBetween(2, 5)).get(); - waitForNoBlocksOnNodes(); - - try { - for (String node : nodesIds) { - logger.debug("--> creating a new instance of the collector"); - IndexRecoveryCollector collector = newIndexRecoveryCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data if node is master"); - enableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> starting graceful period and checks that the collector can still collect data if node is master"); - beginGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> ending graceful period and checks that the collector cannot collect data"); - endGracefulPeriod(); - assertCannotCollect(collector); - - logger.debug("--> disabling license and checks that the collector cannot collect data"); - disableLicense(); - assertCannotCollect(collector); - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - public void testEmptyCluster() throws Exception { final String node = internalCluster().startNode(Settings.builder().put(MonitoringSettings.INDICES.getKey(), Strings.EMPTY_ARRAY)); @@ -211,7 +171,7 @@ public class IndexRecoveryCollectorTests extends AbstractCollectorTestCase { return new IndexRecoveryCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId), + internalCluster().getInstance(XPackLicenseState.class, nodeId), securedClient(nodeId)); } } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollectorTests.java index bbfa57e01d4..816f4ff8148 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndexStatsCollectorTests.java @@ -11,16 +11,15 @@ import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; import java.util.Collection; import java.util.Iterator; -import java.util.List; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.hamcrest.Matchers.equalTo; @@ -181,55 +180,6 @@ public class IndexStatsCollectorTests extends AbstractCollectorTestCase { } } - public void testIndexStatsCollectorWithLicensing() throws Exception { - List nodesIds = internalCluster().startNodesAsync(randomIntBetween(2, 5)).get(); - waitForNoBlocksOnNodes(); - - try { - final int nbDocs = randomIntBetween(1, 20); - for (int i = 0; i < nbDocs; i++) { - client().prepareIndex("test", "test").setSource("num", i).get(); - } - - securedFlush(); - securedRefresh(); - securedEnsureGreen("test"); - - for (String node : nodesIds) { - logger.debug("--> creating a new instance of the collector"); - IndexStatsCollector collector = newIndexStatsCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data if node is master"); - enableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> starting graceful period and checks that the collector can still collect data if node is master"); - beginGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> ending graceful period and checks that the collector cannot collect data"); - endGracefulPeriod(); - assertCannotCollect(collector); - - logger.debug("--> disabling license and checks that the collector cannot collect data"); - disableLicense(); - assertCannotCollect(collector); - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - private IndexStatsCollector newIndexStatsCollector() { // This collector runs on master node only return newIndexStatsCollector(internalCluster().getMasterName()); @@ -240,7 +190,7 @@ public class IndexStatsCollectorTests extends AbstractCollectorTestCase { return new IndexStatsCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId), + internalCluster().getInstance(XPackLicenseState.class, nodeId), securedClient(nodeId)); } } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollectorTests.java index bd4706ec065..6f2e42ebb7d 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/indices/IndicesStatsCollectorTests.java @@ -13,16 +13,15 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; import org.hamcrest.Matchers; import java.util.Collection; -import java.util.List; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.hamcrest.Matchers.equalTo; @@ -155,55 +154,6 @@ public class IndicesStatsCollectorTests extends AbstractCollectorTestCase { assertThat(indicesStats.getIndices().keySet(), hasSize(nbIndices)); } - public void testIndicesStatsCollectorWithLicensing() throws Exception { - List nodesIds = internalCluster().startNodesAsync(randomIntBetween(2, 5)).get(); - waitForNoBlocksOnNodes(); - - try { - final int nbDocs = randomIntBetween(1, 20); - for (int i = 0; i < nbDocs; i++) { - client().prepareIndex("test", "test").setSource("num", i).get(); - } - - securedFlush(); - securedRefresh(); - securedEnsureGreen("test"); - - for (String node : nodesIds) { - logger.debug("--> creating a new instance of the collector"); - IndicesStatsCollector collector = newIndicesStatsCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data if node is master"); - enableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> starting graceful period and checks that the collector can still collect data if node is master"); - beginGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> ending graceful period and checks that the collector cannot collect data"); - endGracefulPeriod(); - assertCannotCollect(collector); - - logger.debug("--> disabling license and checks that the collector cannot collect data"); - disableLicense(); - assertCannotCollect(collector); - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - private IndicesStatsCollector newIndicesStatsCollector() { // This collector runs on master node only return newIndicesStatsCollector(internalCluster().getMasterName()); @@ -216,7 +166,7 @@ public class IndicesStatsCollectorTests extends AbstractCollectorTestCase { return new IndicesStatsCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId), + internalCluster().getInstance(XPackLicenseState.class, nodeId), securedClient(nodeId)); } } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollectorTests.java index c2a9067f579..ab6e4c53f2d 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/node/NodeStatsCollectorTests.java @@ -11,9 +11,9 @@ import org.elasticsearch.cluster.routing.allocation.decider.DiskThresholdDecider import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.NodeEnvironment; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -63,41 +63,11 @@ public class NodeStatsCollectorTests extends AbstractCollectorTestCase { } } - public void testNodeStatsCollectorWithLicensing() { - try { - String[] nodes = internalCluster().getNodeNames(); - for (String node : nodes) { - logger.debug("--> creating a new instance of the collector"); - NodeStatsCollector collector = newNodeStatsCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data"); - enableLicense(); - assertCanCollect(collector); - - logger.debug("--> starting graceful period and checks that the collector can still collect data"); - beginGracefulPeriod(); - assertCanCollect(collector); - - logger.debug("--> ending graceful period and checks that the collector cannot collect data"); - endGracefulPeriod(); - assertCannotCollect(collector); - - logger.debug("--> disabling license and checks that the collector cannot collect data"); - disableLicense(); - assertCannotCollect(collector); - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - private NodeStatsCollector newNodeStatsCollector(final String nodeId) { return new NodeStatsCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId), + internalCluster().getInstance(XPackLicenseState.class, nodeId), internalCluster().getInstance(InternalClient.class, nodeId), internalCluster().getInstance(NodeEnvironment.class, nodeId), internalCluster().getInstance(DiskThresholdDecider.class, nodeId)); diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollectorTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollectorTests.java index 6ea921ed419..513b0639d17 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollectorTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/collector/shards/ShardsCollectorTests.java @@ -10,8 +10,8 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.monitoring.MonitoredSystem; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.collector.AbstractCollectorTestCase; import org.elasticsearch.xpack.monitoring.agent.exporter.MonitoringDoc; @@ -160,44 +160,6 @@ public class ShardsCollectorTests extends AbstractCollectorTestCase { } } - public void testShardsCollectorWithLicensing() { - try { - String[] nodes = internalCluster().getNodeNames(); - for (String node : nodes) { - logger.debug("--> creating a new instance of the collector"); - ShardsCollector collector = newShardsCollector(node); - assertNotNull(collector); - - logger.debug("--> enabling license and checks that the collector can collect data if node is master"); - enableLicense(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> starting graceful period and checks that the collector can still collect data if node is master"); - beginGracefulPeriod(); - if (node.equals(internalCluster().getMasterName())) { - assertCanCollect(collector); - } else { - assertCannotCollect(collector); - } - - logger.debug("--> ending graceful period and checks that the collector cannot collect data"); - endGracefulPeriod(); - assertCannotCollect(collector); - - logger.debug("--> disabling license and checks that the collector cannot collect data"); - disableLicense(); - assertCannotCollect(collector); - } - } finally { - // Ensure license is enabled before finishing the test - enableLicense(); - } - } - private ShardsCollector newShardsCollector() { // This collector runs on master node only return newShardsCollector(internalCluster().getMasterName()); @@ -208,6 +170,6 @@ public class ShardsCollectorTests extends AbstractCollectorTestCase { return new ShardsCollector(internalCluster().getInstance(Settings.class, nodeId), internalCluster().getInstance(ClusterService.class, nodeId), internalCluster().getInstance(MonitoringSettings.class, nodeId), - internalCluster().getInstance(MonitoringLicensee.class, nodeId)); + internalCluster().getInstance(XPackLicenseState.class, nodeId)); } } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolverTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolverTests.java index d3db5a06950..88deec5691a 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolverTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/indices/IndexStatsResolverTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.cache.query.QueryCacheStats; +import org.elasticsearch.index.cache.request.RequestCacheStats; import org.elasticsearch.index.engine.SegmentsStats; import org.elasticsearch.index.fielddata.FieldDataStats; import org.elasticsearch.index.merge.MergeStats; @@ -86,6 +87,7 @@ public class IndexStatsResolverTests extends MonitoringIndexNameResolverTestCase CommonStats stats = new CommonStats(); stats.fieldData = new FieldDataStats(); stats.queryCache = new QueryCacheStats(); + stats.requestCache = new RequestCacheStats(); stats.docs = new DocsStats(); stats.store = new StoreStats(); stats.indexing = new IndexingStats(); diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolverTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolverTests.java index 471101f47b1..36dd87a4cd8 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolverTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/resolver/node/NodeStatsResolverTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.common.transport.LocalTransportAddress; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.Index; import org.elasticsearch.index.cache.query.QueryCacheStats; +import org.elasticsearch.index.cache.request.RequestCacheStats; import org.elasticsearch.index.engine.SegmentsStats; import org.elasticsearch.index.fielddata.FieldDataStats; import org.elasticsearch.index.search.stats.SearchStats; @@ -113,6 +114,7 @@ public class NodeStatsResolverTests extends MonitoringIndexNameResolverTestCase< CommonStats stats = new CommonStats(); stats.fieldData = new FieldDataStats(); stats.queryCache = new QueryCacheStats(); + stats.requestCache = new RequestCacheStats(); stats.docs = new DocsStats(); stats.store = new StoreStats(); stats.indexing = new IndexingStats(); diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/settings/MonitoringSettingsTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/settings/MonitoringSettingsTests.java index ef914685f36..9f837906da6 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/settings/MonitoringSettingsTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/agent/settings/MonitoringSettingsTests.java @@ -10,12 +10,16 @@ import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.xpack.MockNetty3Plugin; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.agent.AgentService; import org.elasticsearch.xpack.monitoring.test.MonitoringIntegTestCase; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; @@ -44,6 +48,13 @@ public class MonitoringSettingsTests extends MonitoringIntegTestCase { .build(); } + @Override + protected Collection> nodePlugins() { + ArrayList> plugins = new ArrayList<>(super.nodePlugins()); + plugins.add(MockNetty3Plugin.class); // for http + return plugins; + } + private Settings monitoringSettings() { return Settings.builder() .put(MonitoringSettings.INTERVAL.getKey(), interval) diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/cleaner/CleanerServiceTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/cleaner/CleanerServiceTests.java index 89f2b72af5e..44dd709d0bf 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/cleaner/CleanerServiceTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/cleaner/CleanerServiceTests.java @@ -8,10 +8,10 @@ package org.elasticsearch.xpack.monitoring.cleaner; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -34,7 +34,7 @@ public class CleanerServiceTests extends ESTestCase { @Rule public ExpectedException expectedException = ExpectedException.none(); - private final MonitoringLicensee licensee = mock(MonitoringLicensee.class); + private final XPackLicenseState licenseState = mock(XPackLicenseState.class); private ClusterSettings clusterSettings; private ThreadPool threadPool; @@ -56,39 +56,39 @@ public class CleanerServiceTests extends ESTestCase { TimeValue expected = TimeValue.timeValueHours(1); Settings settings = Settings.builder().put(MonitoringSettings.HISTORY_DURATION.getKey(), expected.getStringRep()).build(); - new CleanerService(settings, clusterSettings, threadPool, licensee); + new CleanerService(settings, clusterSettings, threadPool, licenseState); } public void testGetRetentionWithSettingWithUpdatesAllowed() { TimeValue expected = TimeValue.timeValueHours(25); Settings settings = Settings.builder().put(MonitoringSettings.HISTORY_DURATION.getKey(), expected.getStringRep()).build(); - when(licensee.allowUpdateRetention()).thenReturn(true); + when(licenseState.isUpdateRetentionAllowed()).thenReturn(true); - assertEquals(expected, new CleanerService(settings, clusterSettings, threadPool, licensee).getRetention()); + assertEquals(expected, new CleanerService(settings, clusterSettings, threadPool, licenseState).getRetention()); - verify(licensee).allowUpdateRetention(); + verify(licenseState).isUpdateRetentionAllowed(); } public void testGetRetentionDefaultValueWithNoSettings() { - when(licensee.allowUpdateRetention()).thenReturn(true); + when(licenseState.isUpdateRetentionAllowed()).thenReturn(true); assertEquals(MonitoringSettings.HISTORY_DURATION.get(Settings.EMPTY), - new CleanerService(Settings.EMPTY, clusterSettings, threadPool, licensee).getRetention()); + new CleanerService(Settings.EMPTY, clusterSettings, threadPool, licenseState).getRetention()); - verify(licensee).allowUpdateRetention(); + verify(licenseState).isUpdateRetentionAllowed(); } public void testGetRetentionDefaultValueWithSettingsButUpdatesNotAllowed() { TimeValue notExpected = TimeValue.timeValueHours(25); Settings settings = Settings.builder().put(MonitoringSettings.HISTORY_DURATION.getKey(), notExpected.getStringRep()).build(); - when(licensee.allowUpdateRetention()).thenReturn(false); + when(licenseState.isUpdateRetentionAllowed()).thenReturn(false); assertEquals(MonitoringSettings.HISTORY_DURATION.get(Settings.EMPTY), - new CleanerService(settings, clusterSettings, threadPool, licensee).getRetention()); + new CleanerService(settings, clusterSettings, threadPool, licenseState).getRetention()); - verify(licensee).allowUpdateRetention(); + verify(licenseState).isUpdateRetentionAllowed(); } public void testSetGlobalRetention() { @@ -96,15 +96,15 @@ public class CleanerServiceTests extends ESTestCase { // only thing calling this method and it will use the settings object to validate the time value TimeValue expected = TimeValue.timeValueHours(2); - when(licensee.allowUpdateRetention()).thenReturn(true); + when(licenseState.isUpdateRetentionAllowed()).thenReturn(true); - CleanerService service = new CleanerService(Settings.EMPTY, clusterSettings, threadPool, licensee); + CleanerService service = new CleanerService(Settings.EMPTY, clusterSettings, threadPool, licenseState); service.setGlobalRetention(expected); assertEquals(expected, service.getRetention()); - verify(licensee, times(2)).allowUpdateRetention(); // once by set, once by get + verify(licenseState, times(2)).isUpdateRetentionAllowed(); // once by set, once by get } public void testSetGlobalRetentionAppliesEvenIfLicenseDisallows() { @@ -113,9 +113,9 @@ public class CleanerServiceTests extends ESTestCase { TimeValue expected = TimeValue.timeValueHours(2); // required to be true on the second call for it to see it take effect - when(licensee.allowUpdateRetention()).thenReturn(false).thenReturn(true); + when(licenseState.isUpdateRetentionAllowed()).thenReturn(false).thenReturn(true); - CleanerService service = new CleanerService(Settings.EMPTY, clusterSettings, threadPool, licensee); + CleanerService service = new CleanerService(Settings.EMPTY, clusterSettings, threadPool, licenseState); // uses allow=false service.setGlobalRetention(expected); @@ -123,7 +123,7 @@ public class CleanerServiceTests extends ESTestCase { // uses allow=true assertEquals(expected, service.getRetention()); - verify(licensee, times(2)).allowUpdateRetention(); + verify(licenseState, times(2)).isUpdateRetentionAllowed(); } public void testNextExecutionDelay() { @@ -151,9 +151,9 @@ public class CleanerServiceTests extends ESTestCase { CountDownLatch latch = new CountDownLatch(nbExecutions); logger.debug("--> creates a cleaner service that cleans every second"); - MonitoringLicensee licensee = mock(MonitoringLicensee.class); - when(licensee.cleaningEnabled()).thenReturn(true); - CleanerService service = new CleanerService(Settings.EMPTY, clusterSettings, licensee, threadPool, + XPackLicenseState licenseState = mock(XPackLicenseState.class); + when(licenseState.isMonitoringAllowed()).thenReturn(true); + CleanerService service = new CleanerService(Settings.EMPTY, clusterSettings, licenseState, threadPool, new TestExecutionScheduler(1_000)); logger.debug("--> registers cleaning listener"); diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/license/LicenseIntegrationTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/license/LicenseIntegrationTests.java deleted file mode 100644 index 6d2b17d60d7..00000000000 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/license/LicenseIntegrationTests.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.monitoring.license; - -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.component.LifecycleComponent; -import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Module; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.Licensing; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; -import org.elasticsearch.license.plugin.core.LicensesManagerService; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.rest.RestHandler; -import org.elasticsearch.test.ESIntegTestCase.ClusterScope; -import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; -import org.elasticsearch.xpack.monitoring.test.MonitoringIntegTestCase; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static java.util.Collections.emptyList; -import static org.elasticsearch.test.ESIntegTestCase.Scope.SUITE; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isOneOf; - -@ClusterScope(scope = SUITE, transportClientRatio = 0, numClientNodes = 0) -public class LicenseIntegrationTests extends MonitoringIntegTestCase { - @Override - protected Collection> nodePlugins() { - return Arrays.asList(InternalXPackPlugin.class); - } - - @Override - protected Settings nodeSettings(int nodeOrdinal) { - return Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .build(); - } - - public void testEnableDisableLicense() { - assertThat(getLicensee().getStatus().getLicenseState(), isOneOf(LicenseState.ENABLED, LicenseState.GRACE_PERIOD)); - assertThat(getLicensee().collectionEnabled(), is(true)); - disableLicensing(); - - assertThat(getLicensee().getStatus().getLicenseState(), equalTo(LicenseState.DISABLED)); - assertThat(getLicensee().collectionEnabled(), is(false)); - enableLicensing(); - - assertThat(getLicensee().getStatus().getLicenseState(), isOneOf(LicenseState.ENABLED, LicenseState.GRACE_PERIOD)); - assertThat(getLicensee().collectionEnabled(), is(true)); - } - - private MonitoringLicensee getLicensee() { - MonitoringLicensee licensee = internalCluster().getInstance(MonitoringLicensee.class); - assertNotNull(licensee); - return licensee; - } - - public static void disableLicensing() { - for (MockLicenseService service : internalCluster().getInstances(MockLicenseService.class)) { - service.disable(); - } - } - - public static void enableLicensing() { - for (MockLicenseService service : internalCluster().getInstances(MockLicenseService.class)) { - service.enable(); - } - } - - public static class MockLicensing extends Licensing { - - public MockLicensing() { - super(Settings.EMPTY); - } - - @Override - public Collection nodeModules() { - return Collections.singletonList(new InternalLicenseModule()); - } - - @Override - public List, ? extends ActionResponse>> getActions() { - return emptyList(); - } - - @Override - public List> getRestHandlers() { - return emptyList(); - } - - @Override - public Collection> nodeServices() { - return Collections.emptyList(); - } - - } - - public static class InternalLicenseModule extends AbstractModule { - @Override - protected void configure() { - bind(MockLicenseService.class).asEagerSingleton(); - bind(LicenseeRegistry.class).to(MockLicenseService.class); - bind(LicensesManagerService.class).to(MockLicenseService.class); - } - } - - public static class MockLicenseService extends AbstractComponent implements LicenseeRegistry, LicensesManagerService { - - private final List licensees = new ArrayList<>(); - - @Inject - public MockLicenseService(Settings settings) { - super(settings); - enable(); - } - - @Override - public void register(Licensee licensee) { - licensees.add(licensee); - enable(); - } - - public void enable() { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(License.OperationMode.BASIC, - randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); - } - } - - public void disable() { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(License.OperationMode.BASIC, LicenseState.DISABLED)); - } - } - - @Override - public LicenseState licenseState() { - return null; - } - - @Override - public License getLicense() { - return null; - } - } - - public static class InternalXPackPlugin extends XPackPlugin { - public InternalXPackPlugin(Settings settings) throws IOException { - super(settings); - licensing = new MockLicensing(); - } - } -} diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/license/MonitoringLicenseeTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/license/MonitoringLicenseeTests.java deleted file mode 100644 index 837816ad1e4..00000000000 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/license/MonitoringLicenseeTests.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.monitoring.license; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.AbstractLicenseeTestCase; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee.Status; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; -import org.elasticsearch.xpack.monitoring.MonitoringLicensee; - -import java.util.function.Predicate; - -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -/** - * Tests {@link MonitoringLicensee}. - *

- * If you change the behavior of these tests, then it means that licensing changes for Monitoring! - */ -public class MonitoringLicenseeTests extends AbstractLicenseeTestCase { - private final LicenseeRegistry registry = mock(LicenseeRegistry.class); - private final MonitoringLicensee licensee = new MonitoringLicensee(Settings.EMPTY, registry); - - public void testAcknowledgementMessagesToAnyFromFreeIsNoOp() { - assertEmptyAck(OperationMode.BASIC, randomMode(), licensee); - } - - public void testAcknowledgementMessagesToTrialGoldOrPlatinumFromAnyIsNoOp() { - assertEmptyAck(randomMode(), randomTrialStandardGoldOrPlatinumMode(), licensee); - } - - public void testAcknowledgementMessagesToBasicFromNotBasicNotesLimits() { - OperationMode from = randomFrom(OperationMode.STANDARD, OperationMode.GOLD, OperationMode.PLATINUM, OperationMode.TRIAL); - OperationMode to = OperationMode.BASIC; - - String[] messages = ackLicenseChange(from, to, licensee); - - // leaving messages up to inspection - assertThat(fromToMessage(from, to), messages.length, equalTo(2)); - } - - public void testCollectionEnabledIsTrueForActiveState() { - assertEnabled(randomEnabledOrGracePeriodState(), MonitoringLicensee::collectionEnabled, true); - } - - public void testCollectionEnabledIsFalseForInactiveState() { - assertEnabled(LicenseState.DISABLED, MonitoringLicensee::collectionEnabled, false); - } - - public void testCleaningEnabledIsTrueForActiveState() { - assertEnabled(randomEnabledOrGracePeriodState(), MonitoringLicensee::cleaningEnabled, true); - } - - public void testCleaningEnabledIsFalseForInactiveState() { - assertEnabled(LicenseState.DISABLED, MonitoringLicensee::cleaningEnabled, false); - } - - public void testAllowUpdateRetentionIsTrueForNotBasic() { - OperationMode mode = randomFrom(OperationMode.STANDARD, OperationMode.GOLD, OperationMode.PLATINUM, OperationMode.TRIAL); - assertEnabled(mode, MonitoringLicensee::allowUpdateRetention, true); - } - - public void testAllowUpdateRetentionIsFalseForBasic() { - assertEnabled(OperationMode.BASIC, MonitoringLicensee::allowUpdateRetention, false); - } - - public void testAllowUpdateRetentionIsFalseForMissing() { - assertEnabled(OperationMode.MISSING, MonitoringLicensee::allowUpdateRetention, false); - } - - /** - * Assert that the {@link #licensee} is {@code predicate}d as {@code expected} when setting the {@code state}. - * - * @param state The state that should cause the {@code expected} {@code predicate}. - * @param predicate The method to invoke (expected to be an instance method). - * @param expected The expected outcome given the {@code state} and {@code predicate}. - */ - private void assertEnabled(LicenseState state, Predicate predicate, boolean expected) { - Status status = mock(Status.class); - when(status.getLicenseState()).thenReturn(state); - - licensee.onChange(status); - - assertThat(predicate.test(licensee), equalTo(expected)); - - verify(status).getLicenseState(); - verifyNoMoreInteractions(registry); - } - - /** - * Assert that the {@link #licensee} is {@code predicate}d as {@code expected} when setting the {@code mode}. - * - * @param mode The mode that should cause the {@code expected} {@code predicate}. - * @param predicate The method to invoke (expected to be an instance method). - * @param expected The expected outcome given the {@code mode} and {@code predicate}. - */ - private void assertEnabled(OperationMode mode, Predicate predicate, boolean expected) { - Status status = mock(Status.class); - when(status.getMode()).thenReturn(mode); - - licensee.onChange(status); - - assertThat(predicate.test(licensee), equalTo(expected)); - - verify(status).getMode(); - verifyNoMoreInteractions(registry); - } -} diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringInternalClientTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringInternalClientTests.java index 6c37e15d673..7abe7872210 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringInternalClientTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringInternalClientTests.java @@ -26,7 +26,6 @@ public class MonitoringInternalClientTests extends MonitoringIntegTestCase { protected Settings nodeSettings(int nodeOrdinal) { return Settings.builder() .put(super.nodeSettings(nodeOrdinal)) - .put(NetworkModule.HTTP_ENABLED.getKey(), false) .put(MonitoringSettings.INTERVAL.getKey(), "-1") .build(); } diff --git a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringSettingsFilterTests.java b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringSettingsFilterTests.java index 08f98a20822..c2a4a1bba6d 100644 --- a/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringSettingsFilterTests.java +++ b/elasticsearch/x-pack/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/security/MonitoringSettingsFilterTests.java @@ -11,18 +11,21 @@ import org.elasticsearch.client.Response; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.xpack.MockNetty3Plugin; import org.elasticsearch.xpack.monitoring.MonitoringSettings; import org.elasticsearch.xpack.monitoring.test.MonitoringIntegTestCase; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import java.util.Collections; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; public class MonitoringSettingsFilterTests extends MonitoringIntegTestCase { @@ -42,6 +45,13 @@ public class MonitoringSettingsFilterTests extends MonitoringIntegTestCase { .build(); } + @Override + protected Collection> nodePlugins() { + ArrayList> plugins = new ArrayList<>(super.nodePlugins()); + plugins.add(MockNetty3Plugin.class); // for http + return plugins; + } + public void testGetSettingsFiltered() throws Exception { Header[] headers; if (securityEnabled) { @@ -52,22 +62,20 @@ public class MonitoringSettingsFilterTests extends MonitoringIntegTestCase { } else { headers = new Header[0]; } - try (Response response = getRestClient().performRequest("GET", "/_nodes/settings", - Collections.emptyMap(), null, headers)) { - Map responseMap = JsonXContent.jsonXContent.createParser(response.getEntity().getContent()).map(); + Response response = getRestClient().performRequest("GET", "/_nodes/settings", headers); + Map responseMap = JsonXContent.jsonXContent.createParser(response.getEntity().getContent()).map(); + @SuppressWarnings("unchecked") + Map nodes = (Map) responseMap.get("nodes"); + for (Object node : nodes.values()) { @SuppressWarnings("unchecked") - Map nodes = (Map) responseMap.get("nodes"); - for (Object node : nodes.values()) { - @SuppressWarnings("unchecked") - Map settings = (Map) ((Map) node).get("settings"); - assertThat(extractValue("xpack.monitoring.collection.exporters._http.type", settings), equalTo("http")); - assertThat(extractValue("xpack.monitoring.collection.exporters._http.enabled", settings), equalTo("false")); - assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.auth.username"); - assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.auth.password"); - assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.ssl.truststore.path"); - assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.ssl.truststore.password"); - assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.ssl.hostname_verification"); - } + Map settings = (Map) ((Map) node).get("settings"); + assertThat(extractValue("xpack.monitoring.collection.exporters._http.type", settings), equalTo("http")); + assertThat(extractValue("xpack.monitoring.collection.exporters._http.enabled", settings), equalTo("false")); + assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.auth.username"); + assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.auth.password"); + assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.ssl.truststore.path"); + assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.ssl.truststore.password"); + assertNullSetting(settings, "xpack.monitoring.collection.exporters._http.ssl.hostname_verification"); } } diff --git a/elasticsearch/x-pack/monitoring/src/test/resources/rest-api-spec/test/monitoring/bulk/10_basic.yaml b/elasticsearch/x-pack/monitoring/src/test/resources/rest-api-spec/test/monitoring/bulk/10_basic.yaml index b238b7ffb50..6dad7415668 100644 --- a/elasticsearch/x-pack/monitoring/src/test/resources/rest-api-spec/test/monitoring/bulk/10_basic.yaml +++ b/elasticsearch/x-pack/monitoring/src/test/resources/rest-api-spec/test/monitoring/bulk/10_basic.yaml @@ -10,6 +10,7 @@ setup: cluster.health: index: ".monitoring-data-*" wait_for_active_shards: 1 + timeout: 60s --- "Bulk indexing of monitoring data": diff --git a/elasticsearch/x-pack/security/bin/x-pack/certgen b/elasticsearch/x-pack/security/bin/x-pack/certgen new file mode 100644 index 00000000000..3a148e94351 --- /dev/null +++ b/elasticsearch/x-pack/security/bin/x-pack/certgen @@ -0,0 +1,104 @@ +#!/bin/bash + +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. + +SCRIPT="$0" + +# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path. +while [ -h "$SCRIPT" ] ; do + ls=`ls -ld "$SCRIPT"` + # Drop everything prior to -> + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + SCRIPT="$link" + else + SCRIPT=`dirname "$SCRIPT"`/"$link" + fi +done + +# determine elasticsearch home +ES_HOME=`dirname "$SCRIPT"`/../.. + +# make ELASTICSEARCH_HOME absolute +ES_HOME=`cd "$ES_HOME"; pwd` + +# If an include wasn't specified in the environment, then search for one... +if [ "x$ES_INCLUDE" = "x" ]; then + # Locations (in order) to use when searching for an include file. + for include in /usr/share/elasticsearch/elasticsearch.in.sh \ + /usr/local/share/elasticsearch/elasticsearch.in.sh \ + /opt/elasticsearch/elasticsearch.in.sh \ + ~/.elasticsearch.in.sh \ + "`dirname "$0"`"/../elasticsearch.in.sh \ + "$ES_HOME/bin/elasticsearch.in.sh"; do + if [ -r "$include" ]; then + . "$include" + break + fi + done +# ...otherwise, source the specified include. +elif [ -r "$ES_INCLUDE" ]; then + . "$ES_INCLUDE" +fi + +if [ -x "$JAVA_HOME/bin/java" ]; then + JAVA="$JAVA_HOME/bin/java" +else + JAVA=`which java` +fi + +if [ ! -x "$JAVA" ]; then + echo "Could not find any executable java binary. Please install java in your PATH or set JAVA_HOME" + exit 1 +fi + +if [ -z "$ES_CLASSPATH" ]; then + echo "You must set the ES_CLASSPATH var" >&2 + exit 1 +fi + +if [ -z "$CONF_DIR" ]; then + # Try to read package config files + if [ -f "/etc/sysconfig/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch + + . "/etc/sysconfig/elasticsearch" + elif [ -f "/etc/default/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch + + . "/etc/default/elasticsearch" + fi +fi + +export HOSTNAME=`hostname -s` + +# include x-pack jars in classpath +ES_CLASSPATH="$ES_CLASSPATH:$ES_HOME/plugins/x-pack/*" + +# don't let JAVA_TOOL_OPTIONS slip in (e.g. crazy agents in ubuntu) +# works around https://bugs.launchpad.net/ubuntu/+source/jayatana/+bug/1441487 +if [ "x$JAVA_TOOL_OPTIONS" != "x" ]; then + echo "Warning: Ignoring JAVA_TOOL_OPTIONS=$JAVA_TOOL_OPTIONS" + echo "Please pass JVM parameters via ES_JAVA_OPTS instead" + unset JAVA_TOOL_OPTIONS +fi + +# CONF_FILE setting was removed +if [ ! -z "$CONF_FILE" ]; then + echo "CONF_FILE setting is no longer supported. elasticsearch.yml must be placed in the config directory and cannot be renamed." + exit 1 +fi + +declare -a args=("$@") + +if [ -e "$CONF_DIR" ]; then + args=("${args[@]}" -Edefault.path.conf="$CONF_DIR") +fi + +cd "$ES_HOME" > /dev/null +"$JAVA" $ES_JAVA_OPTS -cp "$ES_CLASSPATH" -Des.path.home="$ES_HOME" org.elasticsearch.xpack.security.ssl.CertificateTool "${args[@]}" +status=$? +cd - > /dev/null +exit $status diff --git a/elasticsearch/x-pack/security/bin/x-pack/certgen.bat b/elasticsearch/x-pack/security/bin/x-pack/certgen.bat new file mode 100644 index 00000000000..7c17d77c330 --- /dev/null +++ b/elasticsearch/x-pack/security/bin/x-pack/certgen.bat @@ -0,0 +1,9 @@ +@echo off + +rem Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +rem or more contributor license agreements. Licensed under the Elastic License; +rem you may not use this file except in compliance with the Elastic License. + +PUSHD "%~dp0" +CALL "%~dp0.in.bat" org.elasticsearch.xpack.security.ssl.CertificateTool %* +POPD diff --git a/elasticsearch/x-pack/security/bin/x-pack/migrate b/elasticsearch/x-pack/security/bin/x-pack/migrate index a4b35234bb3..bc90fc0ad41 100755 --- a/elasticsearch/x-pack/security/bin/x-pack/migrate +++ b/elasticsearch/x-pack/security/bin/x-pack/migrate @@ -59,15 +59,17 @@ if [ -z "$ES_CLASSPATH" ]; then exit 1 fi -# Try to read package config files -if [ -f "/etc/sysconfig/elasticsearch" ]; then - CONF_DIR=/etc/elasticsearch +if [ -z "$CONF_DIR" ]; then + # Try to read package config files + if [ -f "/etc/sysconfig/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch - . "/etc/sysconfig/elasticsearch" -elif [ -f "/etc/default/elasticsearch" ]; then - CONF_DIR=/etc/elasticsearch + . "/etc/sysconfig/elasticsearch" + elif [ -f "/etc/default/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch - . "/etc/default/elasticsearch" + . "/etc/default/elasticsearch" + fi fi export HOSTNAME=`hostname -s` diff --git a/elasticsearch/x-pack/security/bin/x-pack/syskeygen b/elasticsearch/x-pack/security/bin/x-pack/syskeygen index 45115b9acab..3fda163d032 100755 --- a/elasticsearch/x-pack/security/bin/x-pack/syskeygen +++ b/elasticsearch/x-pack/security/bin/x-pack/syskeygen @@ -59,15 +59,17 @@ if [ -z "$ES_CLASSPATH" ]; then exit 1 fi -# Try to read package config files -if [ -f "/etc/sysconfig/elasticsearch" ]; then - CONF_DIR=/etc/elasticsearch +if [ -z "$CONF_DIR" ]; then + # Try to read package config files + if [ -f "/etc/sysconfig/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch - . "/etc/sysconfig/elasticsearch" -elif [ -f "/etc/default/elasticsearch" ]; then - CONF_DIR=/etc/elasticsearch + . "/etc/sysconfig/elasticsearch" + elif [ -f "/etc/default/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch - . "/etc/default/elasticsearch" + . "/etc/default/elasticsearch" + fi fi export HOSTNAME=`hostname -s` diff --git a/elasticsearch/x-pack/security/bin/x-pack/users b/elasticsearch/x-pack/security/bin/x-pack/users index 2018273bc1a..73d24ffc7c9 100755 --- a/elasticsearch/x-pack/security/bin/x-pack/users +++ b/elasticsearch/x-pack/security/bin/x-pack/users @@ -59,15 +59,17 @@ if [ -z "$ES_CLASSPATH" ]; then exit 1 fi -# Try to read package config files -if [ -f "/etc/sysconfig/elasticsearch" ]; then - CONF_DIR=/etc/elasticsearch +if [ -z "$CONF_DIR" ]; then + # Try to read package config files + if [ -f "/etc/sysconfig/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch - . "/etc/sysconfig/elasticsearch" -elif [ -f "/etc/default/elasticsearch" ]; then - CONF_DIR=/etc/elasticsearch + . "/etc/sysconfig/elasticsearch" + elif [ -f "/etc/default/elasticsearch" ]; then + CONF_DIR=/etc/elasticsearch - . "/etc/default/elasticsearch" + . "/etc/default/elasticsearch" + fi fi export HOSTNAME=`hostname -s` diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/InternalClient.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/InternalClient.java index 6236e810346..b6112beb514 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/InternalClient.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/InternalClient.java @@ -5,6 +5,8 @@ */ package org.elasticsearch.xpack.security; +import java.io.IOException; + import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.Action; import org.elasticsearch.action.ActionListener; @@ -13,60 +15,57 @@ import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.client.Client; import org.elasticsearch.client.FilterClient; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.xpack.security.authc.AuthenticationService; -import org.elasticsearch.xpack.security.user.XPackUser; +import org.elasticsearch.node.Node; import org.elasticsearch.threadpool.ThreadPool; - -import java.io.IOException; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authc.AuthenticationService; +import org.elasticsearch.xpack.security.crypto.CryptoService; +import org.elasticsearch.xpack.security.user.XPackUser; /** + * A special filter client for internal node communication which adds the internal xpack user to the headers. + * An optionally secured client for internal node communication. * + * When secured, the XPack user is added to the execution context before each action is executed. */ -public interface InternalClient extends Client { +public class InternalClient extends FilterClient { + private final CryptoService cryptoService; + private final boolean signUserHeader; + private final String nodeName; /** - * An insecured internal client, baseically simply delegates to the normal ES client - * without doing anything extra. + * Constructs an InternalClient. + * If {@code cryptoService} is non-null, the client is secure. Otherwise this client is a passthrough. */ - class Insecure extends FilterClient implements InternalClient { - - @Inject - public Insecure(Settings settings, ThreadPool threadPool, Client in) { - super(settings, threadPool, in); - } + public InternalClient(Settings settings, ThreadPool threadPool, Client in, CryptoService cryptoService) { + super(settings, threadPool, in); + this.cryptoService = cryptoService; + this.signUserHeader = AuthenticationService.SIGN_USER_HEADER.get(settings); + this.nodeName = Node.NODE_NAME_SETTING.get(settings); } - /** - * A secured internal client that binds the internal XPack user to the current - * execution context, before the action is executed. - */ - class Secure extends FilterClient implements InternalClient { + @Override + protected , Response extends ActionResponse, RequestBuilder extends + ActionRequestBuilder> void doExecute( + Action action, Request request, ActionListener listener) { - private final AuthenticationService authcService; - - @Inject - public Secure(Settings settings, ThreadPool threadPool, Client in, AuthenticationService authcService) { - super(settings, threadPool, in); - this.authcService = authcService; + if (cryptoService == null) { + super.doExecute(action, request, listener); + return; } - @Override - protected , Response extends ActionResponse, RequestBuilder extends - ActionRequestBuilder> void doExecute( - Action action, Request request, ActionListener listener) { - - try (ThreadContext.StoredContext ctx = threadPool().getThreadContext().stashContext()) { - try { - authcService.attachUserIfMissing(XPackUser.INSTANCE); - } catch (IOException ioe) { - throw new ElasticsearchException("failed to attach internal user to request", ioe); - } - super.doExecute(action, request, listener); + try (ThreadContext.StoredContext ctx = threadPool().getThreadContext().stashContext()) { + try { + Authentication authentication = new Authentication(XPackUser.INSTANCE, + new Authentication.RealmRef("__attach", "__attach", nodeName), null); + authentication.writeToContextIfMissing(threadPool().getThreadContext(), cryptoService, signUserHeader); + } catch (IOException ioe) { + throw new ElasticsearchException("failed to attach internal user to request", ioe); } + super.doExecute(action, request, listener); } } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 21d29bb730f..4beb83f873c 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -10,17 +10,21 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.support.ActionFilter; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Booleans; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.logging.ESLogger; @@ -34,9 +38,15 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexModule; +import org.elasticsearch.ingest.Processor; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.plugins.ActionPlugin; +import org.elasticsearch.plugins.IngestPlugin; import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.extensions.XPackExtension; import org.elasticsearch.xpack.security.action.SecurityActionModule; import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter; import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheAction; @@ -59,23 +69,34 @@ import org.elasticsearch.xpack.security.action.user.TransportChangePasswordActio import org.elasticsearch.xpack.security.action.user.TransportDeleteUserAction; import org.elasticsearch.xpack.security.action.user.TransportGetUsersAction; import org.elasticsearch.xpack.security.action.user.TransportPutUserAction; -import org.elasticsearch.xpack.security.audit.AuditTrailModule; +import org.elasticsearch.xpack.security.audit.AuditTrail; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail; import org.elasticsearch.xpack.security.audit.index.IndexNameResolver; import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; -import org.elasticsearch.xpack.security.authc.AuthenticationModule; -import org.elasticsearch.xpack.security.authc.InternalAuthenticationService; +import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler; +import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; +import org.elasticsearch.xpack.security.authc.AuthenticationService; +import org.elasticsearch.xpack.security.authc.Realm; import org.elasticsearch.xpack.security.authc.Realms; +import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectoryRealm; +import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; +import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.authc.file.FileRealm; +import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; +import org.elasticsearch.xpack.security.authc.pki.PkiRealm; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import org.elasticsearch.xpack.security.authz.AuthorizationModule; -import org.elasticsearch.xpack.security.authz.InternalAuthorizationService; import org.elasticsearch.xpack.security.authz.accesscontrol.OptOutQueryCache; import org.elasticsearch.xpack.security.authz.accesscontrol.SecurityIndexSearcherWrapper; +import org.elasticsearch.xpack.security.authz.accesscontrol.SetSecurityUserProcessor; +import org.elasticsearch.xpack.security.authz.AuthorizationService; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; import org.elasticsearch.xpack.security.authz.store.FileRolesStore; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; +import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.rest.SecurityRestModule; import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction; @@ -88,15 +109,16 @@ import org.elasticsearch.xpack.security.rest.action.user.RestChangePasswordActio import org.elasticsearch.xpack.security.rest.action.user.RestDeleteUserAction; import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction; import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction; +import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.xpack.security.ssl.SSLConfiguration; -import org.elasticsearch.xpack.security.ssl.SSLModule; +import org.elasticsearch.xpack.security.ssl.ServerSSLService; import org.elasticsearch.xpack.security.support.OptionalSettings; import org.elasticsearch.xpack.security.transport.SecurityClientTransportService; import org.elasticsearch.xpack.security.transport.SecurityServerTransportService; import org.elasticsearch.xpack.security.transport.SecurityTransportModule; import org.elasticsearch.xpack.security.transport.filter.IPFilter; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.xpack.security.user.AnonymousUser; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -107,7 +129,7 @@ import static java.util.Collections.singletonList; /** * */ -public class Security implements ActionPlugin { +public class Security implements ActionPlugin, IngestPlugin { private static final ESLogger logger = Loggers.getLogger(XPackPlugin.class); @@ -115,14 +137,24 @@ public class Security implements ActionPlugin { public static final String DLS_FLS_FEATURE = "security.dls_fls"; public static final Setting> USER_SETTING = OptionalSettings.createString(setting("user"), Property.NodeScope); + public static final Setting AUDIT_ENABLED_SETTING = + Setting.boolSetting(featureEnabledSetting("audit"), false, Property.NodeScope); + public static final Setting> AUDIT_OUTPUTS_SETTING = + Setting.listSetting(setting("audit.outputs"), + s -> s.getAsMap().containsKey(setting("audit.outputs")) ? + Collections.emptyList() : Collections.singletonList(LoggingAuditTrail.NAME), + Function.identity(), Property.NodeScope); + private final Settings settings; + private final Environment env; private final boolean enabled; private final boolean transportClientMode; - private SecurityLicenseState securityLicenseState; + private final XPackLicenseState licenseState; private final CryptoService cryptoService; - public Security(Settings settings, Environment env) throws IOException { + public Security(Settings settings, Environment env, XPackLicenseState licenseState) throws IOException { this.settings = settings; + this.env = env; this.transportClientMode = XPackPlugin.transportClientMode(settings); this.enabled = XPackPlugin.featureEnabled(settings, NAME, true); if (enabled && transportClientMode == false) { @@ -131,12 +163,17 @@ public class Security implements ActionPlugin { } else { cryptoService = null; } + this.licenseState = licenseState; } public CryptoService getCryptoService() { return cryptoService; } + public boolean isEnabled() { + return enabled; + } + public Collection nodeModules() { List modules = new ArrayList<>(); @@ -144,18 +181,26 @@ public class Security implements ActionPlugin { if (enabled == false) { return modules; } - modules.add(new SecurityModule(settings, securityLicenseState)); modules.add(new SecurityTransportModule(settings)); - modules.add(new SSLModule(settings)); + modules.add(b -> { + // for transport client we still must inject these ssl classes with guice + b.bind(ServerSSLService.class).toProvider(Providers.of(null)); + b.bind(ClientSSLService.class).toInstance( + new ClientSSLService(settings, null, new SSLConfiguration.Global(settings), null)); + }); + return modules; } - - modules.add(new AuthenticationModule(settings)); - modules.add(new AuthorizationModule(settings)); + modules.add(b -> XPackPlugin.bindFeatureSet(b, SecurityFeatureSet.class)); + if (enabled == false) { - modules.add(b -> b.bind(CryptoService.class).toProvider(Providers.of(null))); - modules.add(new SecurityModule(settings, securityLicenseState)); - modules.add(new AuditTrailModule(settings)); + modules.add(b -> { + b.bind(CryptoService.class).toProvider(Providers.of(null)); + b.bind(Realms.class).toProvider(Providers.of(null)); // for SecurityFeatureSet + b.bind(CompositeRolesStore.class).toProvider(Providers.of(null)); // for SecurityFeatureSet + b.bind(AuditTrailService.class) + .toInstance(new AuditTrailService(settings, Collections.emptyList(), licenseState)); + }); modules.add(new SecurityTransportModule(settings)); return modules; } @@ -163,31 +208,123 @@ public class Security implements ActionPlugin { // we can't load that at construction time since the license plugin might not have been loaded at that point // which might not be the case during Plugin class instantiation. Once nodeModules are pulled // everything should have been loaded - securityLicenseState = new SecurityLicenseState(); - modules.add(b -> b.bind(CryptoService.class).toInstance(cryptoService)); - modules.add(new SecurityModule(settings, securityLicenseState)); - modules.add(new AuditTrailModule(settings)); + modules.add(b -> { + b.bind(CryptoService.class).toInstance(cryptoService); + if (auditingEnabled(settings)) { + b.bind(AuditTrail.class).to(AuditTrailService.class); // interface used by some actions... + } + }); modules.add(new SecurityRestModule(settings)); modules.add(new SecurityActionModule(settings)); modules.add(new SecurityTransportModule(settings)); - modules.add(new SSLModule(settings)); return modules; } - public Collection> nodeServices() { - if (enabled == false || transportClientMode == true) { + public Collection createComponents(InternalClient client, ThreadPool threadPool, ClusterService clusterService, + ResourceWatcherService resourceWatcherService, List extensions) { + if (enabled == false) { return Collections.emptyList(); } - List> list = new ArrayList<>(); + AnonymousUser.initialize(settings); // TODO: this is sketchy...testing is difficult b/c it is static.... - //TODO why only focus on file audit logs? shouldn't we just check if audit trail is enabled in general? - if (AuditTrailModule.fileAuditLoggingEnabled(settings) == true) { - list.add(LoggingAuditTrail.class); + List components = new ArrayList<>(); + final SecurityContext securityContext = new SecurityContext(settings, threadPool, cryptoService); + components.add(securityContext); + + final SSLConfiguration.Global globalSslConfig = new SSLConfiguration.Global(settings); + final ClientSSLService clientSSLService = new ClientSSLService(settings, env, globalSslConfig, resourceWatcherService); + final ServerSSLService serverSSLService = new ServerSSLService(settings, env, globalSslConfig, resourceWatcherService); + components.add(clientSSLService); + components.add(serverSSLService); + + // realms construction + final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client, threadPool); + final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore); + Map realmFactories = new HashMap<>(); + realmFactories.put(FileRealm.TYPE, config -> new FileRealm(config, resourceWatcherService)); + realmFactories.put(NativeRealm.TYPE, config -> new NativeRealm(config, nativeUsersStore)); + realmFactories.put(ActiveDirectoryRealm.TYPE, + config -> new ActiveDirectoryRealm(config, resourceWatcherService, clientSSLService)); + realmFactories.put(LdapRealm.TYPE, config -> new LdapRealm(config, resourceWatcherService, clientSSLService)); + realmFactories.put(PkiRealm.TYPE, config -> new PkiRealm(config, resourceWatcherService)); + for (XPackExtension extension : extensions) { + Map newRealms = extension.getRealms(); + for (Map.Entry entry : newRealms.entrySet()) { + if (realmFactories.put(entry.getKey(), entry.getValue()) != null) { + throw new IllegalArgumentException("Realm type [" + entry.getKey() + "] is already registered"); + } + } } - list.add(SecurityLicensee.class); - list.add(FileRolesStore.class); - list.add(Realms.class); - return list; + final Realms realms = new Realms(settings, env, realmFactories, licenseState, reservedRealm); + components.add(nativeUsersStore); + components.add(realms); + + // audit trails construction + IndexAuditTrail indexAuditTrail = null; + Set auditTrails = new LinkedHashSet<>(); + if (AUDIT_ENABLED_SETTING.get(settings)) { + List outputs = AUDIT_OUTPUTS_SETTING.get(settings); + if (outputs.isEmpty()) { + throw new IllegalArgumentException("Audit logging is enabled but there are zero output types in " + + AUDIT_ENABLED_SETTING.getKey()); + } + + for (String output : outputs) { + switch (output) { + case LoggingAuditTrail.NAME: + auditTrails.add(new LoggingAuditTrail(settings, clusterService, threadPool)); + break; + case IndexAuditTrail.NAME: + indexAuditTrail = new IndexAuditTrail(settings, client, threadPool, clusterService); + auditTrails.add(indexAuditTrail); + break; + default: + throw new IllegalArgumentException("Unknown audit trail output [" + output + "]"); + } + } + } + final AuditTrailService auditTrailService = + new AuditTrailService(settings, auditTrails.stream().collect(Collectors.toList()), licenseState); + components.add(auditTrailService); + + AuthenticationFailureHandler failureHandler = null; + String extensionName = null; + for (XPackExtension extension : extensions) { + AuthenticationFailureHandler extensionFailureHandler = extension.getAuthenticationFailureHandler(); + if (extensionFailureHandler != null && failureHandler != null) { + throw new IllegalStateException("Extensions [" + extensionName +"] and [" + extension.name() + "] " + + "both set an authentication failure handler"); + } + failureHandler = extensionFailureHandler; + extensionName = extension.name(); + } + if (failureHandler == null) { + logger.debug("Using default authentication failure handler"); + failureHandler = new DefaultAuthenticationFailureHandler(); + } else { + logger.debug("Using authentication failure handler from extension [" + extensionName + "]"); + } + + final AuthenticationService authcService = new AuthenticationService(settings, realms, auditTrailService, + cryptoService, failureHandler, threadPool); + components.add(authcService); + + final FileRolesStore fileRolesStore = new FileRolesStore(settings, env, resourceWatcherService); + final NativeRolesStore nativeRolesStore = new NativeRolesStore(settings, client, threadPool); + final ReservedRolesStore reservedRolesStore = new ReservedRolesStore(securityContext); + final CompositeRolesStore allRolesStore = new CompositeRolesStore(fileRolesStore, nativeRolesStore, reservedRolesStore); + final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService, + auditTrailService, failureHandler, threadPool); + components.add(fileRolesStore); // has lifecycle + components.add(nativeRolesStore); // used by roles actions + components.add(reservedRolesStore); // used by roles actions + components.add(allRolesStore); // for SecurityFeatureSet + components.add(authzService); + + components.add(new SecurityLifecycleService(settings, clusterService, threadPool, indexAuditTrail, + nativeUsersStore, nativeRolesStore, client)); + + return components; } public Settings additionalSettings() { @@ -204,7 +341,7 @@ public class Security implements ActionPlugin { settingsBuilder.put(NetworkModule.TRANSPORT_TYPE_KEY, Security.NAME); settingsBuilder.put(NetworkModule.TRANSPORT_SERVICE_TYPE_KEY, Security.NAME); settingsBuilder.put(NetworkModule.HTTP_TYPE_SETTING.getKey(), Security.NAME); - SecurityNettyHttpServerTransport.overrideSettings(settingsBuilder, settings); + SecurityNetty3HttpServerTransport.overrideSettings(settingsBuilder, settings); addUserSettings(settings, settingsBuilder); addTribeSettings(settings, settingsBuilder); return settingsBuilder.build(); @@ -220,7 +357,7 @@ public class Security implements ActionPlugin { SSLConfiguration.Global.addSettings(settingsList); // transport settings - SecurityNettyTransport.addSettings(settingsList); + SecurityNetty3Transport.addSettings(settingsList); if (transportClientMode) { return settingsList; @@ -233,7 +370,10 @@ public class Security implements ActionPlugin { IPFilter.addSettings(settingsList); // audit settings - AuditTrailModule.addSettings(settingsList); + settingsList.add(AUDIT_ENABLED_SETTING); + settingsList.add(AUDIT_OUTPUTS_SETTING); + LoggingAuditTrail.registerSettings(settingsList); + IndexAuditTrail.registerSettings(settingsList); // authentication settings FileRolesStore.addSettings(settingsList); @@ -241,11 +381,11 @@ public class Security implements ActionPlugin { Realms.addSettings(settingsList); NativeUsersStore.addSettings(settingsList); NativeRolesStore.addSettings(settingsList); - InternalAuthenticationService.addSettings(settingsList); - InternalAuthorizationService.addSettings(settingsList); + AuthenticationService.addSettings(settingsList); + AuthorizationService.addSettings(settingsList); // HTTP settings - SecurityNettyHttpServerTransport.addSettings(settingsList); + SecurityNetty3HttpServerTransport.addSettings(settingsList); // encryption settings CryptoService.addSettings(settingsList); @@ -281,12 +421,13 @@ public class Security implements ActionPlugin { return; } - assert securityLicenseState != null; + assert licenseState != null; if (flsDlsEnabled(settings)) { - module.setSearcherWrapper((indexService) -> new SecurityIndexSearcherWrapper(indexService.getIndexSettings(), - indexService.newQueryShardContext(), indexService.mapperService(), - indexService.cache().bitsetFilterCache(), indexService.getIndexServices().getThreadPool().getThreadContext(), - securityLicenseState)); + module.setSearcherWrapper(indexService -> + new SecurityIndexSearcherWrapper(indexService.getIndexSettings(), indexService.newQueryShardContext(), + indexService.mapperService(), indexService.cache().bitsetFilterCache(), + indexService.getIndexServices().getThreadPool().getThreadContext(), licenseState, + indexService.getIndexServices().getScriptService())); } if (transportClientMode == false) { /* We need to forcefully overwrite the query cache implementation to use security's opt out query cache implementation. @@ -343,20 +484,25 @@ public class Security implements ActionPlugin { RestChangePasswordAction.class); } + @Override + public Map getProcessors(Processor.Parameters parameters) { + return Collections.singletonMap(SetSecurityUserProcessor.TYPE, new SetSecurityUserProcessor.Factory(parameters.threadContext)); + } + public void onModule(NetworkModule module) { if (transportClientMode) { if (enabled) { - module.registerTransport(Security.NAME, SecurityNettyTransport.class); + module.registerTransport(Security.NAME, SecurityNetty3Transport.class); module.registerTransportService(Security.NAME, SecurityClientTransportService.class); } return; } if (enabled) { - module.registerTransport(Security.NAME, SecurityNettyTransport.class); + module.registerTransport(Security.NAME, SecurityNetty3Transport.class); module.registerTransportService(Security.NAME, SecurityServerTransportService.class); - module.registerHttpTransport(Security.NAME, SecurityNettyHttpServerTransport.class); + module.registerHttpTransport(Security.NAME, SecurityNetty3HttpServerTransport.class); } } @@ -460,13 +606,29 @@ public class Security implements ActionPlugin { return XPackPlugin.featureEnabledSetting("security." + feature); } + public static boolean auditingEnabled(Settings settings) { + return AUDIT_ENABLED_SETTING.get(settings); + } + + public static boolean indexAuditLoggingEnabled(Settings settings) { + if (auditingEnabled(settings)) { + List outputs = AUDIT_OUTPUTS_SETTING.get(settings); + for (String output : outputs) { + if (output.equals(IndexAuditTrail.NAME)) { + return true; + } + } + } + return false; + } + static void validateAutoCreateIndex(Settings settings) { String value = settings.get("action.auto_create_index"); if (value == null) { return; } - final boolean indexAuditingEnabled = AuditTrailModule.indexAuditLoggingEnabled(settings); + final boolean indexAuditingEnabled = Security.indexAuditLoggingEnabled(settings); final String auditIndex = indexAuditingEnabled ? "," + IndexAuditTrail.INDEX_NAME_PREFIX + "*" : ""; String errorMessage = LoggerMessageFormat.format("the [action.auto_create_index] setting value [{}] is too" + " restrictive. disable [action.auto_create_index] or set it to " + diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityContext.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityContext.java index 090cdebc455..2942d135272 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityContext.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityContext.java @@ -5,110 +5,58 @@ */ package org.elasticsearch.xpack.security; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.inject.Inject; +import java.io.IOException; + +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.AuthenticationService; +import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.threadpool.ThreadPool; - -import java.io.IOException; -import java.util.concurrent.Callable; /** - * + * A lightweight utility that can find the current user and authentication information for the local thread. */ -public interface SecurityContext { +public class SecurityContext { - void executeAs(User user, Runnable runnable); + private final ESLogger logger; + private final ThreadContext threadContext; + private final CryptoService cryptoService; + private final boolean signUserHeader; - V executeAs(User user, Callable callable); - - User getUser(); - - Authentication getAuthentication(); - - default boolean hasAuthentication() { - return getAuthentication() != null; + /** + * Creates a new security context. + * If cryptoService is null, security is disabled and {@link #getUser()} + * and {@link #getAuthentication()} will always return null. + */ + public SecurityContext(Settings settings, ThreadPool threadPool, CryptoService cryptoService) { + this.logger = Loggers.getLogger(getClass(), settings); + this.threadContext = threadPool.getThreadContext(); + this.cryptoService = cryptoService; + this.signUserHeader = AuthenticationService.SIGN_USER_HEADER.get(settings); } - class Insecure implements SecurityContext { - - public static final Insecure INSTANCE = new Insecure(); - - private Insecure() { - } - - @Override - public void executeAs(User user, Runnable runnable) { - runnable.run(); - } - - @Override - public V executeAs(User user, Callable callable) { - try { - return callable.call(); - } catch (Exception e) { - throw new ElasticsearchException(e); - } - } - - @Override - public User getUser() { - return null; - } - - @Override - public Authentication getAuthentication() { - return null; - } + /** Returns the current user information, or null if the current request has no authentication info. */ + public User getUser() { + Authentication authentication = getAuthentication(); + return authentication == null ? null : authentication.getUser(); } - class Secure implements SecurityContext { - - private final ThreadContext threadContext; - private final AuthenticationService authcService; - - @Inject - public Secure(ThreadPool threadPool, AuthenticationService authcService) { - this.threadContext = threadPool.getThreadContext(); - this.authcService = authcService; + /** Returns the authentication information, or null if the current request has no authentication info. */ + public Authentication getAuthentication() { + if (cryptoService == null) { + return null; } - - public void executeAs(User user, Runnable runnable) { - try (ThreadContext.StoredContext ctx = threadContext.stashContext()) { - setUser(user); - runnable.run(); - } - } - - public V executeAs(User user, Callable callable) { - try (ThreadContext.StoredContext ctx = threadContext.stashContext()) { - setUser(user); - return callable.call(); - } catch (Exception e) { - throw new ElasticsearchException(e); - } - } - - @Override - public User getUser() { - Authentication authentication = authcService.getCurrentAuthentication(); - return authentication == null ? null : authentication.getUser(); - } - - @Override - public Authentication getAuthentication() { - return authcService.getCurrentAuthentication(); - } - - private void setUser(User user) { - try { - authcService.attachUserIfMissing(user); - } catch (IOException | IllegalArgumentException e) { - throw new ElasticsearchException("failed to attach user to request", e); - } + try { + return Authentication.readFromContext(threadContext, cryptoService, signUserHeader); + } catch (IOException e) { + // TODO: this seems bogus, the only way to get an ioexception here is from a corrupt or tampered + // auth header, which should be be audited? + logger.error("failed to read authentication", e); + return null; } } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java index 8ebc8a6a6d4..e1b99c86602 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityFeatureSet.java @@ -13,16 +13,18 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.Realm; import org.elasticsearch.xpack.security.authc.Realms; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.XPackFeatureSet; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; import org.elasticsearch.xpack.security.authz.store.RolesStore; import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import java.io.IOException; import java.util.ArrayList; @@ -39,11 +41,11 @@ public class SecurityFeatureSet implements XPackFeatureSet { private final Settings settings; private final boolean enabled; - private final SecurityLicenseState licenseState; + private final XPackLicenseState licenseState; @Nullable private final Realms realms; @Nullable - private final RolesStore rolesStore; + private final CompositeRolesStore rolesStore; @Nullable private final IPFilter ipFilter; @Nullable @@ -52,8 +54,8 @@ public class SecurityFeatureSet implements XPackFeatureSet { private final CryptoService cryptoService; @Inject - public SecurityFeatureSet(Settings settings, @Nullable SecurityLicenseState licenseState, - @Nullable Realms realms, NamedWriteableRegistry namedWriteableRegistry, @Nullable RolesStore rolesStore, + public SecurityFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable Realms realms, + NamedWriteableRegistry namedWriteableRegistry, @Nullable CompositeRolesStore rolesStore, @Nullable IPFilter ipFilter, @Nullable AuditTrailService auditTrailService, @Nullable CryptoService cryptoService) { this.enabled = Security.enabled(settings); @@ -79,7 +81,7 @@ public class SecurityFeatureSet implements XPackFeatureSet { @Override public boolean available() { - return licenseState != null && licenseState.authenticationAndAuthorizationEnabled(); + return licenseState != null && licenseState.isAuthAllowed(); } @Override @@ -122,8 +124,8 @@ public class SecurityFeatureSet implements XPackFeatureSet { static Map sslUsage(Settings settings) { Map map = new HashMap<>(2); - map.put("http", Collections.singletonMap("enabled", SecurityNettyHttpServerTransport.SSL_SETTING.get(settings))); - map.put("transport", Collections.singletonMap("enabled", SecurityNettyTransport.SSL_SETTING.get(settings))); + map.put("http", Collections.singletonMap("enabled", SecurityNetty3HttpServerTransport.SSL_SETTING.get(settings))); + map.put("transport", Collections.singletonMap("enabled", SecurityNetty3Transport.SSL_SETTING.get(settings))); return map; } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLicenseState.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLicenseState.java deleted file mode 100644 index b62f2f6275d..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLicenseState.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee.Status; - - -/** - * This class serves to decouple security code that needs to check the license state from the {@link SecurityLicensee} as the - * tight coupling causes issues with guice injection and circular dependencies - */ -public class SecurityLicenseState { - - // we initialize the licensee status to enabled with trial operation mode to ensure no - // legitimate requests are blocked before initial license plugin notification - protected volatile Status status = Status.ENABLED; - - /** - * @return true if authentication and authorization should be enabled. this does not indicate what realms are available - * @see SecurityLicenseState#enabledRealmType() for the enabled realms - */ - public boolean authenticationAndAuthorizationEnabled() { - OperationMode mode = status.getMode(); - return mode == OperationMode.STANDARD || mode == OperationMode.GOLD || mode == OperationMode.PLATINUM - || mode == OperationMode.TRIAL; - } - - /** - * @return true if IP filtering should be enabled - */ - public boolean ipFilteringEnabled() { - OperationMode mode = status.getMode(); - return mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL; - } - - /** - * @return true if auditing should be enabled - */ - public boolean auditingEnabled() { - OperationMode mode = status.getMode(); - return mode == OperationMode.GOLD || mode == OperationMode.PLATINUM || mode == OperationMode.TRIAL; - } - - /** - * Indicates whether the stats and health API calls should be allowed. If a license is expired and past the grace - * period then we deny these calls. - * - * @return true if the license allows for the stats and health APIs to be used. - */ - public boolean statsAndHealthEnabled() { - return status.getLicenseState() != LicenseState.DISABLED; - } - - /** - * Determine if Document Level Security (DLS) and Field Level Security (FLS) should be enabled. - *

- * DLS and FLS are only disabled when the mode is not: - *

    - *
  • {@link OperationMode#PLATINUM}
  • - *
  • {@link OperationMode#TRIAL}
  • - *
- * Note: This does not consider the state of the license so that Security does not suddenly leak information! - * - * @return {@code true} to enable DLS and FLS. Otherwise {@code false}. - */ - public boolean documentAndFieldLevelSecurityEnabled() { - Status status = this.status; - return status.getMode() == OperationMode.TRIAL || status.getMode() == OperationMode.PLATINUM; - } - - /** - * @return the type of realms that are enabled based on the license {@link OperationMode} - */ - public EnabledRealmType enabledRealmType() { - OperationMode mode = status.getMode(); - switch (mode) { - case PLATINUM: - case TRIAL: - return EnabledRealmType.ALL; - case GOLD: - return EnabledRealmType.DEFAULT; - case STANDARD: - return EnabledRealmType.NATIVE; - default: - return EnabledRealmType.NONE; - } - } - - void updateStatus(Status status) { - this.status = status; - } - - public enum EnabledRealmType { - NONE, - NATIVE, - DEFAULT, - ALL - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLicensee.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLicensee.java deleted file mode 100644 index 95d9d8e5864..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLicensee.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.plugin.core.AbstractLicenseeComponent; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; - -/** - * - */ -public class SecurityLicensee extends AbstractLicenseeComponent implements Licensee { - - private final boolean isTribeNode; - private final SecurityLicenseState securityLicenseState; - - @Inject - public SecurityLicensee(Settings settings, LicenseeRegistry clientService, SecurityLicenseState securityLicenseState) { - super(settings, Security.NAME, clientService); - this.securityLicenseState = securityLicenseState; - this.isTribeNode = settings.getGroups("tribe", true).isEmpty() == false; - } - - @Override - public void onChange(Status status) { - super.onChange(status); - securityLicenseState.updateStatus(status); - } - - @Override - public String[] expirationMessages() { - return new String[]{ - "Cluster health, cluster stats and indices stats operations are blocked", - "All data operations (read and write) continue to work" - }; - } - - @Override - public String[] acknowledgmentMessages(License currentLicense, License newLicense) { - switch (newLicense.operationMode()) { - case BASIC: - if (currentLicense != null) { - switch (currentLicense.operationMode()) { - case TRIAL: - case STANDARD: - case GOLD: - case PLATINUM: - return new String[] { - "The following X-Pack security functionality will be disabled: authentication, authorization, " + - "ip filtering, and auditing. Please restart your node after applying the license.", - "Field and document level access control will be disabled.", - "Custom realms will be ignored." - }; - } - } - break; - case GOLD: - if (currentLicense != null) { - switch (currentLicense.operationMode()) { - case BASIC: - case STANDARD: - // ^^ though technically it was already disabled, it's not bad to remind them - case TRIAL: - case PLATINUM: - return new String[] { - "Field and document level access control will be disabled.", - "Custom realms will be ignored." - }; - } - } - break; - case STANDARD: - if (currentLicense != null) { - switch (currentLicense.operationMode()) { - case BASIC: - // ^^ though technically it was already disabled, it's not bad to remind them - case GOLD: - case PLATINUM: - case TRIAL: - return new String[] { - "Authentication will be limited to the native realms.", - "IP filtering and auditing will be disabled.", - "Field and document level access control will be disabled.", - "Custom realms will be ignored." - }; - } - } - } - return Strings.EMPTY_ARRAY; - } - - @Override - protected void doStart() throws ElasticsearchException { - // we rely on the initial licensee state to be enabled with trial operation mode - // to ensure no operation is blocked due to not registering the licensee on a - // tribe node - if (isTribeNode == false) { - super.doStart(); - } - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java index f977bee3a51..7a2194d92a3 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java @@ -6,19 +6,17 @@ package org.elasticsearch.xpack.security; import org.elasticsearch.cluster.ClusterChangedEvent; -import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterStateListener; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.LifecycleListener; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Provider; +import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.AbstractRunnable; -import org.elasticsearch.xpack.security.audit.AuditTrailModule; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail; import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; -import org.elasticsearch.threadpool.ThreadPool; /** * This class is used to provide a lifecycle for services that is based on the cluster's state @@ -39,10 +37,9 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust private final NativeUsersStore nativeUserStore; private final NativeRolesStore nativeRolesStore; - @Inject public SecurityLifecycleService(Settings settings, ClusterService clusterService, ThreadPool threadPool, - IndexAuditTrail indexAuditTrail, NativeUsersStore nativeUserStore, - NativeRolesStore nativeRolesStore, Provider clientProvider) { + @Nullable IndexAuditTrail indexAuditTrail, NativeUsersStore nativeUserStore, + NativeRolesStore nativeRolesStore, InternalClient client) { super(settings); this.settings = settings; this.threadPool = threadPool; @@ -54,7 +51,7 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust clusterService.add(this); clusterService.add(nativeUserStore); clusterService.add(nativeRolesStore); - clusterService.add(new SecurityTemplateService(settings, clusterService, clientProvider, threadPool)); + clusterService.add(new SecurityTemplateService(settings, clusterService, client, threadPool)); clusterService.addLifecycleListener(new LifecycleListener() { @Override @@ -111,7 +108,7 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust } try { - if (AuditTrailModule.indexAuditLoggingEnabled(settings) && + if (Security.indexAuditLoggingEnabled(settings) && indexAuditTrail.state() == IndexAuditTrail.State.INITIALIZED) { if (indexAuditTrail.canStart(event, master)) { threadPool.generic().execute(new AbstractRunnable() { @@ -146,19 +143,23 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust } catch (Exception e) { logger.error("failed to stop native roles module", e); } - try { - indexAuditTrail.stop(); - } catch (Exception e) { - logger.error("failed to stop audit trail module", e); + if (indexAuditTrail != null) { + try { + indexAuditTrail.stop(); + } catch (Exception e) { + logger.error("failed to stop audit trail module", e); + } } } public void close() { // There is no .close() method for the roles module - try { - indexAuditTrail.close(); - } catch (Exception e) { - logger.error("failed to close audit trail module", e); + if (indexAuditTrail != null) { + try { + indexAuditTrail.close(); + } catch (Exception e) { + logger.error("failed to close audit trail module", e); + } } } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityModule.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityModule.java deleted file mode 100644 index acff075a3ef..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityModule.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.common.inject.util.Providers; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.support.AbstractSecurityModule; -import org.elasticsearch.xpack.XPackPlugin; - -/** - * - */ -public class SecurityModule extends AbstractSecurityModule { - - private final SecurityLicenseState securityLicenseState; - - public SecurityModule(Settings settings, SecurityLicenseState securityLicenseState) { - super(settings); - this.securityLicenseState = securityLicenseState; - } - - @Override - protected void configure(boolean clientMode) { - if (clientMode) { - return; - } - - if (securityLicenseState != null) { - bind(SecurityLicenseState.class).toInstance(securityLicenseState); - } else { - bind(SecurityLicenseState.class).toProvider(Providers.of(null)); - } - - XPackPlugin.bindFeatureSet(binder(), SecurityFeatureSet.class); - - if (securityEnabled) { - bind(SecurityContext.Secure.class).asEagerSingleton(); - bind(SecurityContext.class).to(SecurityContext.Secure.class); - bind(SecurityLifecycleService.class).asEagerSingleton(); - bind(InternalClient.Secure.class).asEagerSingleton(); - bind(InternalClient.class).to(InternalClient.Secure.class); - } else { - bind(SecurityContext.class).toInstance(SecurityContext.Insecure.INSTANCE); - bind(InternalClient.Insecure.class).asEagerSingleton(); - bind(InternalClient.class).to(InternalClient.Insecure.class); - } - } - -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityTemplateService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityTemplateService.java index d4d580a4978..c4594daaa23 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityTemplateService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/SecurityTemplateService.java @@ -37,19 +37,18 @@ public class SecurityTemplateService extends AbstractComponent implements Cluste public static final String SECURITY_TEMPLATE_NAME = "security-index-template"; private final ThreadPool threadPool; - private final Provider clientProvider; + private final InternalClient client; private final AtomicBoolean templateCreationPending = new AtomicBoolean(false); public SecurityTemplateService(Settings settings, ClusterService clusterService, - Provider clientProvider, ThreadPool threadPool) { + InternalClient client, ThreadPool threadPool) { super(settings); this.threadPool = threadPool; - this.clientProvider = clientProvider; + this.client = client; clusterService.add(this); } private void createSecurityTemplate() { - final Client client = clientProvider.get(); try (InputStream is = getClass().getResourceAsStream("/" + SECURITY_TEMPLATE_NAME + ".json")) { ByteArrayOutputStream out = new ByteArrayOutputStream(); Streams.copy(is, out); @@ -83,7 +82,8 @@ public class SecurityTemplateService extends AbstractComponent implements Cluste if (securityIndexRouting == null) { if (event.localNodeMaster()) { ClusterState state = event.state(); - // TODO for the future need to add some checking in the event the template needs to be updated... + // norelease we need to add some checking in the event the template needs to be updated and also the mappings need to be + // updated on index too! IndexTemplateMetaData templateMeta = state.metaData().templates().get(SECURITY_TEMPLATE_NAME); final boolean createTemplate = (templateMeta == null); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 74e8bdda4c1..a75f3ecd208 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -18,21 +18,22 @@ import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.license.plugin.core.LicenseUtils; +import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.action.SecurityActionMapper; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authc.AuthenticationService; +import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor; import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.authc.AuthenticationService; -import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationUtils; import org.elasticsearch.xpack.security.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.xpack.security.crypto.CryptoService; -import org.elasticsearch.xpack.security.SecurityLicenseState; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; @@ -54,13 +55,13 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil private final AuditTrail auditTrail; private final SecurityActionMapper actionMapper; private final Set requestInterceptors; - private final SecurityLicenseState licenseState; + private final XPackLicenseState licenseState; private final ThreadContext threadContext; private final SecurityContext securityContext; @Inject public SecurityActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, - CryptoService cryptoService, AuditTrail auditTrail, SecurityLicenseState licenseState, + CryptoService cryptoService, AuditTrailService auditTrail, XPackLicenseState licenseState, SecurityActionMapper actionMapper, Set requestInterceptors, ThreadPool threadPool, SecurityContext securityContext) { super(settings); @@ -82,7 +83,7 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil A functional requirement - when the license of security is disabled (invalid/expires), security will continue to operate normally, except all read operations will be blocked. */ - if (!licenseState.statsAndHealthEnabled() && LICENSE_EXPIRATION_ACTION_MATCHER.test(action)) { + if (licenseState.isStatsAndHealthAllowed() == false && LICENSE_EXPIRATION_ACTION_MATCHER.test(action)) { logger.error("blocking [{}] operation due to expired license. Cluster health, cluster stats and indices stats \n" + "operations are blocked on license expiration. All data operations (read and write) continue to work. \n" + "If you have a new license, please update it. Otherwise, please reach out to your support contact.", action); @@ -92,9 +93,9 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil // only restore the context if it is not empty. This is needed because sometimes a response is sent to the user // and then a cleanup action is executed (like for search without a scroll) final ThreadContext.StoredContext original = threadContext.newStoredContext(); - final boolean restoreOriginalContext = securityContext.hasAuthentication(); + final boolean restoreOriginalContext = securityContext.getAuthentication() != null; try { - if (licenseState.authenticationAndAuthorizationEnabled()) { + if (licenseState.isAuthAllowed()) { if (AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action)) { try (ThreadContext.StoredContext ctx = threadContext.stashContext()) { applyInternal(task, action, request, new SigningListener(this, listener, original), chain); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java index ee6db9b477d..8f974e688fe 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java @@ -15,7 +15,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.authz.InternalAuthorizationService; +import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequest; @@ -34,7 +34,7 @@ public class BulkRequestInterceptor extends AbstractComponent implements Request } public void intercept(BulkRequest request, User user) { - IndicesAccessControl indicesAccessControl = threadContext.getTransient(InternalAuthorizationService.INDICES_PERMISSIONS_KEY); + IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY); for (IndicesRequest indicesRequest : request.subRequests()) { for (String index : indicesRequest.indices()) { IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java index e81e7e4cd15..b2a1c159708 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java @@ -12,7 +12,7 @@ import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.authz.InternalAuthorizationService; +import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; import java.util.Collections; @@ -42,7 +42,7 @@ public abstract class FieldAndDocumentLevelSecurityRequestInterceptor e throw new IllegalArgumentException(LoggerMessageFormat.format("Expected a request of type [{}] or [{}] but got [{}] instead", CompositeIndicesRequest.class, IndicesRequest.class, request.getClass())); } - IndicesAccessControl indicesAccessControl = threadContext.getTransient(InternalAuthorizationService.INDICES_PERMISSIONS_KEY); + IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY); for (IndicesRequest indicesRequest : indicesRequests) { for (String index : indicesRequest.indices()) { IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java index 70a2b0f3104..1b7a9b4bfc7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java @@ -8,18 +8,19 @@ package org.elasticsearch.xpack.security.action.role; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.WriteRequest; -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.support.MetadataUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import static org.elasticsearch.action.ValidateActions.addValidationError; @@ -33,6 +34,7 @@ public class PutRoleRequest extends ActionRequest implements Wri private List indicesPrivileges = new ArrayList<>(); private String[] runAs = Strings.EMPTY_ARRAY; private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE; + private Map metadata; public PutRoleRequest() { } @@ -43,6 +45,10 @@ public class PutRoleRequest extends ActionRequest implements Wri if (name == null) { validationException = addValidationError("role name is missing", validationException); } + if (metadata != null && MetadataUtils.containsReservedMetadata(metadata)) { + validationException = + addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]", validationException); + } return validationException; } @@ -86,6 +92,10 @@ public class PutRoleRequest extends ActionRequest implements Wri return refreshPolicy; } + public void metadata(Map metadata) { + this.metadata = metadata; + } + public String name() { return name; } @@ -102,6 +112,10 @@ public class PutRoleRequest extends ActionRequest implements Wri return runAs; } + public Map metadata() { + return metadata; + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); @@ -114,6 +128,7 @@ public class PutRoleRequest extends ActionRequest implements Wri } runAs = in.readStringArray(); refreshPolicy = RefreshPolicy.readFrom(in); + metadata = in.readMap(); } @Override @@ -127,12 +142,14 @@ public class PutRoleRequest extends ActionRequest implements Wri } out.writeStringArray(runAs); refreshPolicy.writeTo(out); + out.writeMap(metadata); } RoleDescriptor roleDescriptor() { return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]), - runAs); + runAs, + metadata); } } \ No newline at end of file diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java index 9360e982451..410c6668613 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java @@ -12,6 +12,8 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import java.util.Map; + /** * Builder for requests to add a role to the administrative index */ @@ -33,6 +35,7 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder metadata) { + request.metadata(metadata); + return this; + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java index 07f196cbbf9..e6af432f949 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java @@ -18,88 +18,6 @@ import java.net.InetAddress; */ public interface AuditTrail { - AuditTrail NOOP = new AuditTrail() { - - static final String NAME = "noop"; - - @Override - public String name() { - return NAME; - } - - @Override - public void anonymousAccessDenied(String action, TransportMessage message) { - } - - @Override - public void anonymousAccessDenied(RestRequest request) { - } - - @Override - public void authenticationFailed(RestRequest request) { - } - - @Override - public void authenticationFailed(String action, TransportMessage message) { - } - - @Override - public void authenticationFailed(AuthenticationToken token, String action, TransportMessage message) { - } - - @Override - public void authenticationFailed(AuthenticationToken token, RestRequest request) { - } - - @Override - public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage message) { - } - - @Override - public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) { - } - - @Override - public void accessGranted(User user, String action, TransportMessage message) { - } - - @Override - public void accessDenied(User user, String action, TransportMessage message) { - } - - @Override - public void tamperedRequest(RestRequest request) { - } - - @Override - public void tamperedRequest(String action, TransportMessage message) { - } - - @Override - public void tamperedRequest(User user, String action, TransportMessage request) { - } - - @Override - public void connectionGranted(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - } - - @Override - public void connectionDenied(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - } - - @Override - public void runAsGranted(User user, String action, TransportMessage message) { - } - - @Override - public void runAsDenied(User user, String action, TransportMessage message) { - } - - @Override - public void runAsDenied(User user, RestRequest request) { - } - }; - String name(); void anonymousAccessDenied(String action, TransportMessage message); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailModule.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailModule.java deleted file mode 100644 index de26c226e7c..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailModule.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.audit; - -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.inject.multibindings.Multibinder; -import org.elasticsearch.common.inject.util.Providers; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail; -import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; -import org.elasticsearch.xpack.security.support.AbstractSecurityModule; - -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.function.Function; - -import static org.elasticsearch.xpack.security.Security.featureEnabledSetting; -import static org.elasticsearch.xpack.security.Security.setting; - -/** - * - */ -public class AuditTrailModule extends AbstractSecurityModule.Node { - - public static final Setting ENABLED_SETTING = - Setting.boolSetting(featureEnabledSetting("audit"), false, Property.NodeScope); - public static final Setting> OUTPUTS_SETTING = - Setting.listSetting(setting("audit.outputs"), - s -> s.getAsMap().containsKey(setting("audit.outputs")) ? - Collections.emptyList() : Collections.singletonList(LoggingAuditTrail.NAME), - Function.identity(), Property.NodeScope); - private final boolean enabled; - - public AuditTrailModule(Settings settings) { - super(settings); - enabled = ENABLED_SETTING.get(settings); - } - - @Override - protected void configureNode() { - List outputs = OUTPUTS_SETTING.get(settings); - if (securityEnabled == false || enabled == false || outputs.isEmpty()) { - bind(AuditTrailService.class).toProvider(Providers.of(null)); - bind(AuditTrail.class).toInstance(AuditTrail.NOOP); - return; - } - - bind(AuditTrailService.class).asEagerSingleton(); - bind(AuditTrail.class).to(AuditTrailService.class); - Multibinder binder = Multibinder.newSetBinder(binder(), AuditTrail.class); - Set uniqueOutputs = Sets.newHashSet(outputs); - for (String output : uniqueOutputs) { - switch (output) { - case LoggingAuditTrail.NAME: - binder.addBinding().to(LoggingAuditTrail.class); - bind(LoggingAuditTrail.class).asEagerSingleton(); - break; - case IndexAuditTrail.NAME: - binder.addBinding().to(IndexAuditTrail.class); - bind(IndexAuditTrail.class).asEagerSingleton(); - break; - default: - throw new ElasticsearchException("unknown audit trail output [" + output + "]"); - } - } - } - - public static boolean auditingEnabled(Settings settings) { - return ENABLED_SETTING.get(settings); - } - - public static boolean indexAuditLoggingEnabled(Settings settings) { - if (auditingEnabled(settings)) { - List outputs = OUTPUTS_SETTING.get(settings); - for (String output : outputs) { - if (output.equals(IndexAuditTrail.NAME)) { - return true; - } - } - } - return false; - } - - public static boolean fileAuditLoggingEnabled(Settings settings) { - if (auditingEnabled(settings)) { - List outputs = OUTPUTS_SETTING.get(settings); - for (String output : outputs) { - if (output.equals(LoggingAuditTrail.NAME)) { - return true; - } - } - } - return false; - } - - public static void addSettings(List> settings) { - settings.add(ENABLED_SETTING); - settings.add(OUTPUTS_SETTING); - LoggingAuditTrail.registerSettings(settings); - IndexAuditTrail.registerSettings(settings); - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java index 87e1e50ee40..7154967ebe9 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java @@ -5,44 +5,49 @@ */ package org.elasticsearch.xpack.security.audit; +import java.net.InetAddress; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.xpack.security.SecurityLicenseState; -import org.elasticsearch.xpack.security.user.User; +import org.elasticsearch.transport.TransportMessage; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.AuthenticationToken; import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule; -import org.elasticsearch.transport.TransportMessage; - -import java.net.InetAddress; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import org.elasticsearch.xpack.security.user.User; /** * */ public class AuditTrailService extends AbstractComponent implements AuditTrail { - private final SecurityLicenseState securityLicenseState; - final AuditTrail[] auditTrails; + private final XPackLicenseState licenseState; + final List auditTrails; @Override public String name() { return "service"; } - @Inject - public AuditTrailService(Settings settings, Set auditTrails, SecurityLicenseState licenseState) { + public AuditTrailService(Settings settings, List auditTrails, XPackLicenseState licenseState) { super(settings); - this.auditTrails = auditTrails.toArray(new AuditTrail[auditTrails.size()]); - this.securityLicenseState = licenseState; + this.auditTrails = Collections.unmodifiableList(auditTrails); + this.licenseState = licenseState; + } + + /** Returns the audit trail implementations that this service delegates to. */ + public List getAuditTrails() { + return auditTrails; } @Override public void anonymousAccessDenied(String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.anonymousAccessDenied(action, message); } @@ -51,7 +56,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void anonymousAccessDenied(RestRequest request) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.anonymousAccessDenied(request); } @@ -60,7 +65,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void authenticationFailed(RestRequest request) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.authenticationFailed(request); } @@ -69,7 +74,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void authenticationFailed(String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.authenticationFailed(action, message); } @@ -78,7 +83,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void authenticationFailed(AuthenticationToken token, String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.authenticationFailed(token, action, message); } @@ -87,7 +92,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void authenticationFailed(String realm, AuthenticationToken token, String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.authenticationFailed(realm, token, action, message); } @@ -96,7 +101,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void authenticationFailed(AuthenticationToken token, RestRequest request) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.authenticationFailed(token, request); } @@ -105,7 +110,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.authenticationFailed(realm, token, request); } @@ -114,7 +119,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void accessGranted(User user, String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.accessGranted(user, action, message); } @@ -123,7 +128,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void accessDenied(User user, String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.accessDenied(user, action, message); } @@ -139,7 +144,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void tamperedRequest(String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.tamperedRequest(action, message); } @@ -148,7 +153,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void tamperedRequest(User user, String action, TransportMessage request) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.tamperedRequest(user, action, request); } @@ -157,7 +162,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void connectionGranted(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.connectionGranted(inetAddress, profile, rule); } @@ -166,7 +171,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void connectionDenied(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.connectionDenied(inetAddress, profile, rule); } @@ -175,7 +180,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void runAsGranted(User user, String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.runAsGranted(user, action, message); } @@ -184,7 +189,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void runAsDenied(User user, String action, TransportMessage message) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.runAsDenied(user, action, message); } @@ -193,7 +198,7 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { @Override public void runAsDenied(User user, RestRequest request) { - if (securityLicenseState.auditingEnabled()) { + if (licenseState.isAuditingAllowed()) { for (AuditTrail auditTrail : auditTrails) { auditTrail.runAsDenied(user, request); } @@ -202,8 +207,8 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail { public Map usageStats() { Map map = new HashMap<>(2); - map.put("enabled", AuditTrailModule.ENABLED_SETTING.get(settings)); - map.put("outputs", AuditTrailModule.OUTPUTS_SETTING.get(settings)); + map.put("enabled", Security.AUDIT_ENABLED_SETTING.get(settings)); + map.put("outputs", Security.AUDIT_OUTPUTS_SETTING.get(settings)); return map; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java index e3b00743df7..d254a67b517 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrail.java @@ -19,6 +19,7 @@ import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.Client; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.cluster.ClusterChangedEvent; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateListener; @@ -27,8 +28,8 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Provider; import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; @@ -54,9 +55,8 @@ import org.elasticsearch.xpack.security.authz.privilege.SystemPrivilege; import org.elasticsearch.xpack.security.rest.RemoteHostHeader; import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportMessage; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -150,17 +150,15 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl private final AtomicReference state = new AtomicReference<>(State.INITIALIZED); private final String nodeName; - private final Provider clientProvider; + private final Client client; private final BlockingQueue eventQueue; private final QueueConsumer queueConsumer; - private final Transport transport; private final ThreadPool threadPool; private final Lock putMappingLock = new ReentrantLock(); private final ClusterService clusterService; private final boolean indexToRemoteCluster; private BulkProcessor bulkProcessor; - private Client client; private IndexNameResolver.Rollover rollover; private String nodeHostName; private String nodeHostAddress; @@ -171,12 +169,8 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl return NAME; } - @Inject - public IndexAuditTrail(Settings settings, Transport transport, - Provider clientProvider, ThreadPool threadPool, ClusterService clusterService) { + public IndexAuditTrail(Settings settings, InternalClient client, ThreadPool threadPool, ClusterService clusterService) { super(settings); - this.clientProvider = clientProvider; - this.transport = transport; this.threadPool = threadPool; this.clusterService = clusterService; this.nodeName = settings.get("name"); @@ -199,6 +193,13 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl } this.indexToRemoteCluster = REMOTE_CLIENT_SETTINGS.get(settings).names().size() > 0; + if (indexToRemoteCluster == false) { + // in the absence of client settings for remote indexing, fall back to the client that was passed in. + this.client = client; + } else { + this.client = initializeRemoteClient(settings, logger); + } + } public State state() { @@ -223,16 +224,6 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl */ public synchronized boolean canStart(ClusterChangedEvent event, boolean master) { if (indexToRemoteCluster) { - try { - if (client == null) { - initializeClient(); - } - } catch (Exception e) { - logger.error("failed to initialize client for remote indexing. index based output is disabled", e); - state.set(State.FAILED); - return false; - } - ClusterStateResponse response = client.admin().cluster().prepareState().execute().actionGet(); return canStart(response.getState(), master); } @@ -277,12 +268,8 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl */ public void start(boolean master) { if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { - this.nodeHostName = transport.boundAddress().publishAddress().getHost(); - this.nodeHostAddress = transport.boundAddress().publishAddress().getAddress(); - - if (client == null) { - initializeClient(); - } + this.nodeHostName = clusterService.localNode().getHostName(); + this.nodeHostAddress = clusterService.localNode().getHostAddress(); if (master) { putTemplate(customAuditIndexSettings(settings)); @@ -545,7 +532,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl Message msg = new Message().start(); common("transport", type, msg.builder); - originAttributes(message, msg.builder, transport, threadPool.getThreadContext()); + originAttributes(message, msg.builder, clusterService.localNode(), threadPool.getThreadContext()); if (action != null) { msg.builder.field(Field.ACTION, action); @@ -577,7 +564,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl Message msg = new Message().start(); common("transport", type, msg.builder); - originAttributes(message, msg.builder, transport, threadPool.getThreadContext()); + originAttributes(message, msg.builder, clusterService.localNode(), threadPool.getThreadContext()); if (action != null) { msg.builder.field(Field.ACTION, action); @@ -672,8 +659,8 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl return builder; } - private static XContentBuilder originAttributes(TransportMessage message, XContentBuilder builder, Transport transport, ThreadContext - threadContext) throws IOException { + private static XContentBuilder originAttributes(TransportMessage message, XContentBuilder builder, + DiscoveryNode localNode, ThreadContext threadContext) throws IOException { // first checking if the message originated in a rest call InetSocketAddress restAddress = RemoteHostHeader.restRemoteAddress(threadContext); @@ -698,7 +685,7 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl // the call was originated locally on this node builder.field(Field.ORIGIN_TYPE, "local_node"); - builder.field(Field.ORIGIN_ADDRESS, transport.boundAddress().publishAddress().getAddress()); + builder.field(Field.ORIGIN_ADDRESS, localNode.getHostAddress()); return builder; } @@ -718,56 +705,48 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl return eventQueue.peek(); } - private void initializeClient() { - if (indexToRemoteCluster == false) { - // in the absence of client settings for remote indexing, fall back to the client that was passed in. - this.client = clientProvider.get(); - } else { - Settings clientSettings = REMOTE_CLIENT_SETTINGS.get(settings); - String[] hosts = clientSettings.getAsArray("hosts"); - if (hosts.length == 0) { - throw new ElasticsearchException("missing required setting " + - "[" + REMOTE_CLIENT_SETTINGS.getKey() + ".hosts] for remote audit log indexing"); - } - - if (clientSettings.get("cluster.name", "").isEmpty()) { - throw new ElasticsearchException("missing required setting " + - "[" + REMOTE_CLIENT_SETTINGS.getKey() + ".cluster.name] for remote audit log indexing"); - } - - List> hostPortPairs = new ArrayList<>(); - - for (String host : hosts) { - List hostPort = Arrays.asList(host.trim().split(":")); - if (hostPort.size() != 1 && hostPort.size() != 2) { - logger.warn("invalid host:port specified: [{}] for setting [{}.hosts]", REMOTE_CLIENT_SETTINGS.getKey(), host); - } - hostPortPairs.add(new Tuple<>(hostPort.get(0), hostPort.size() == 2 ? Integer.valueOf(hostPort.get(1)) : 9300)); - } - - if (hostPortPairs.size() == 0) { - throw new ElasticsearchException("no valid host:port pairs specified for setting [" - + REMOTE_CLIENT_SETTINGS.getKey() + ".hosts]"); - } - final Settings theClientSetting = clientSettings.filter((s) -> s.startsWith("hosts") == false); // hosts is not a valid setting - final TransportClient transportClient = TransportClient.builder() - .settings(Settings.builder() - .put("node.name", DEFAULT_CLIENT_NAME + "-" + Node.NODE_NAME_SETTING.get(settings)) - .put(theClientSetting)) - .addPlugin(XPackPlugin.class) - .build(); - for (Tuple pair : hostPortPairs) { - try { - transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(pair.v1()), pair.v2())); - } catch (UnknownHostException e) { - throw new ElasticsearchException("could not find host {}", e, pair.v1()); - } - } - - this.client = transportClient; - logger.info("forwarding audit events to remote cluster [{}] using hosts [{}]", - clientSettings.get("cluster.name", ""), hostPortPairs.toString()); + private static Client initializeRemoteClient(Settings settings, ESLogger logger) { + Settings clientSettings = REMOTE_CLIENT_SETTINGS.get(settings); + String[] hosts = clientSettings.getAsArray("hosts"); + if (hosts.length == 0) { + throw new ElasticsearchException("missing required setting " + + "[" + REMOTE_CLIENT_SETTINGS.getKey() + ".hosts] for remote audit log indexing"); } + + if (clientSettings.get("cluster.name", "").isEmpty()) { + throw new ElasticsearchException("missing required setting " + + "[" + REMOTE_CLIENT_SETTINGS.getKey() + ".cluster.name] for remote audit log indexing"); + } + + List> hostPortPairs = new ArrayList<>(); + + for (String host : hosts) { + List hostPort = Arrays.asList(host.trim().split(":")); + if (hostPort.size() != 1 && hostPort.size() != 2) { + logger.warn("invalid host:port specified: [{}] for setting [{}.hosts]", REMOTE_CLIENT_SETTINGS.getKey(), host); + } + hostPortPairs.add(new Tuple<>(hostPort.get(0), hostPort.size() == 2 ? Integer.valueOf(hostPort.get(1)) : 9300)); + } + + if (hostPortPairs.size() == 0) { + throw new ElasticsearchException("no valid host:port pairs specified for setting [" + + REMOTE_CLIENT_SETTINGS.getKey() + ".hosts]"); + } + final Settings theClientSetting = clientSettings.filter((s) -> s.startsWith("hosts") == false); // hosts is not a valid setting + final TransportClient transportClient = new XPackTransportClient(Settings.builder() + .put("node.name", DEFAULT_CLIENT_NAME + "-" + Node.NODE_NAME_SETTING.get(settings)) + .put(theClientSetting).build()); + for (Tuple pair : hostPortPairs) { + try { + transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(pair.v1()), pair.v2())); + } catch (UnknownHostException e) { + throw new ElasticsearchException("could not find host {}", e, pair.v1()); + } + } + + logger.info("forwarding audit events to remote cluster [{}] using hosts [{}]", + clientSettings.get("cluster.name", ""), hostPortPairs.toString()); + return transportClient; } Settings customAuditIndexSettings(Settings nodeSettings) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java index 3db017b9804..2b74749b5fd 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java @@ -5,6 +5,9 @@ */ package org.elasticsearch.xpack.security.audit.logfile; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.Lifecycle; import org.elasticsearch.common.component.LifecycleListener; @@ -44,7 +47,7 @@ import static org.elasticsearch.xpack.security.Security.setting; /** * */ -public class LoggingAuditTrail extends AbstractLifecycleComponent implements AuditTrail { +public class LoggingAuditTrail extends AbstractComponent implements AuditTrail { public static final String NAME = "logfile"; public static final Setting HOST_ADDRESS_SETTING = @@ -55,7 +58,7 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud Setting.boolSetting(setting("audit.logfile.prefix.emit_node_name"), true, Property.NodeScope); private final ESLogger logger; - private final Transport transport; + private final ClusterService clusterService; private final ThreadContext threadContext; private String prefix; @@ -65,44 +68,22 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud return NAME; } - @Inject - public LoggingAuditTrail(Settings settings, Transport transport, ThreadPool threadPool) { - this(settings, transport, Loggers.getLogger(LoggingAuditTrail.class), threadPool.getThreadContext()); + public LoggingAuditTrail(Settings settings, ClusterService clusterService, ThreadPool threadPool) { + this(settings, clusterService, Loggers.getLogger(LoggingAuditTrail.class), threadPool.getThreadContext()); } - LoggingAuditTrail(Settings settings, Transport transport, ESLogger logger, ThreadContext threadContext) { - this("", settings, transport, logger, threadContext); - } - - LoggingAuditTrail(String prefix, Settings settings, Transport transport, ESLogger logger, ThreadContext threadContext) { + LoggingAuditTrail(Settings settings, ClusterService clusterService, ESLogger logger, ThreadContext threadContext) { super(settings); this.logger = logger; - this.prefix = prefix; - this.transport = transport; + this.clusterService = clusterService; this.threadContext = threadContext; } - - @Override - protected void doStart() { - if (transport.lifecycleState() == Lifecycle.State.STARTED) { - prefix = resolvePrefix(settings, transport); - } else { - transport.addLifecycleListener(new LifecycleListener() { - @Override - public void afterStart() { - prefix = resolvePrefix(settings, transport); - } - }); + private String getPrefix() { + if (prefix == null) { + prefix = resolvePrefix(settings, clusterService.localNode()); } - } - - @Override - protected void doStop() { - } - - @Override - protected void doClose() { + return prefix; } @Override @@ -110,19 +91,20 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud String indices = indicesString(message); if (indices != null) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [anonymous_access_denied]\t{}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), action, indices, message.getClass().getSimpleName()); + logger.debug("{}[transport] [anonymous_access_denied]\t{}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, indices, + message.getClass().getSimpleName()); } else { - logger.warn("{}[transport] [anonymous_access_denied]\t{}, action=[{}], indices=[{}]", prefix, originAttributes(message, - transport, threadContext), action, indices); + logger.warn("{}[transport] [anonymous_access_denied]\t{}, action=[{}], indices=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [anonymous_access_denied]\t{}, action=[{}], request=[{}]", prefix, originAttributes(message, - transport, threadContext), action, message.getClass().getSimpleName()); + logger.debug("{}[transport] [anonymous_access_denied]\t{}, action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, message.getClass().getSimpleName()); } else { - logger.warn("{}[transport] [anonymous_access_denied]\t{}, action=[{}]", prefix, originAttributes(message, transport, - threadContext), action); + logger.warn("{}[transport] [anonymous_access_denied]\t{}, action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action); } } } @@ -130,10 +112,10 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud @Override public void anonymousAccessDenied(RestRequest request) { if (logger.isDebugEnabled()) { - logger.debug("{}[rest] [anonymous_access_denied]\t{}, uri=[{}], request_body=[{}]", prefix, hostAttributes(request), request - .uri(), restRequestContent(request)); + logger.debug("{}[rest] [anonymous_access_denied]\t{}, uri=[{}], request_body=[{}]", getPrefix(), + hostAttributes(request), request.uri(), restRequestContent(request)); } else { - logger.warn("{}[rest] [anonymous_access_denied]\t{}, uri=[{}]", prefix, hostAttributes(request), request.uri()); + logger.warn("{}[rest] [anonymous_access_denied]\t{}, uri=[{}]", getPrefix(), hostAttributes(request), request.uri()); } } @@ -143,19 +125,20 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud if (indices != null) { if (logger.isDebugEnabled()) { logger.debug("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], indices=[{}], request=[{}]", - prefix, originAttributes(message, transport, threadContext), token.principal(), action, indices, message.getClass - ().getSimpleName()); + getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), token.principal(), + action, indices, message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], indices=[{}]", prefix, - originAttributes(message, transport, threadContext), token.principal(), action, indices); + logger.error("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], indices=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), token.principal(), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), token.principal(), action, message.getClass().getSimpleName()); + logger.debug("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), token.principal(), action, + message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}]", prefix, originAttributes(message, - transport, threadContext), token.principal(), action); + logger.error("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), token.principal(), action); } } } @@ -163,10 +146,10 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud @Override public void authenticationFailed(RestRequest request) { if (logger.isDebugEnabled()) { - logger.debug("{}[rest] [authentication_failed]\t{}, uri=[{}], request_body=[{}]", prefix, hostAttributes(request), request - .uri(), restRequestContent(request)); + logger.debug("{}[rest] [authentication_failed]\t{}, uri=[{}], request_body=[{}]", getPrefix(), hostAttributes(request), + request.uri(), restRequestContent(request)); } else { - logger.error("{}[rest] [authentication_failed]\t{}, uri=[{}]", prefix, hostAttributes(request), request.uri()); + logger.error("{}[rest] [authentication_failed]\t{}, uri=[{}]", getPrefix(), hostAttributes(request), request.uri()); } } @@ -175,19 +158,20 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud String indices = indicesString(message); if (indices != null) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [authentication_failed]\t{}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), action, indices, message.getClass().getSimpleName()); + logger.debug("{}[transport] [authentication_failed]\t{}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, indices, + message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [authentication_failed]\t{}, action=[{}], indices=[{}]", prefix, originAttributes(message, - transport, threadContext), action, indices); + logger.error("{}[transport] [authentication_failed]\t{}, action=[{}], indices=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [authentication_failed]\t{}, action=[{}], request=[{}]", prefix, originAttributes(message, - transport, threadContext), action, message.getClass().getSimpleName()); + logger.debug("{}[transport] [authentication_failed]\t{}, action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [authentication_failed]\t{}, action=[{}]", prefix, originAttributes(message, transport, - threadContext), action); + logger.error("{}[transport] [authentication_failed]\t{}, action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action); } } } @@ -195,11 +179,11 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud @Override public void authenticationFailed(AuthenticationToken token, RestRequest request) { if (logger.isDebugEnabled()) { - logger.debug("{}[rest] [authentication_failed]\t{}, principal=[{}], uri=[{}], request_body=[{}]", prefix, hostAttributes - (request), token.principal(), request.uri(), restRequestContent(request)); + logger.debug("{}[rest] [authentication_failed]\t{}, principal=[{}], uri=[{}], request_body=[{}]", getPrefix(), + hostAttributes(request), token.principal(), request.uri(), restRequestContent(request)); } else { - logger.error("{}[rest] [authentication_failed]\t{}, principal=[{}], uri=[{}]", prefix, hostAttributes(request), token - .principal(), request.uri()); + logger.error("{}[rest] [authentication_failed]\t{}, principal=[{}], uri=[{}]", getPrefix(), hostAttributes(request), + token.principal(), request.uri()); } } @@ -209,12 +193,12 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud String indices = indicesString(message); if (indices != null) { logger.trace("{}[transport] [authentication_failed]\trealm=[{}], {}, principal=[{}], action=[{}], indices=[{}], " + - "request=[{}]", prefix, realm, originAttributes(message, transport, threadContext), token.principal(), action, - indices, message.getClass().getSimpleName()); + "request=[{}]", getPrefix(), realm, originAttributes(message, clusterService.localNode(), threadContext), + token.principal(), action, indices, message.getClass().getSimpleName()); } else { - logger.trace("{}[transport] [authentication_failed]\trealm=[{}], {}, principal=[{}], action=[{}], request=[{}]", prefix, - realm, originAttributes(message, transport, threadContext), token.principal(), action, message.getClass() - .getSimpleName()); + logger.trace("{}[transport] [authentication_failed]\trealm=[{}], {}, principal=[{}], action=[{}], request=[{}]", + getPrefix(), realm, originAttributes(message, clusterService.localNode(), threadContext), token.principal(), + action, message.getClass().getSimpleName()); } } } @@ -222,8 +206,8 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud @Override public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) { if (logger.isTraceEnabled()) { - logger.trace("{}[rest] [authentication_failed]\trealm=[{}], {}, principal=[{}], uri=[{}], request_body=[{}]", prefix, realm, - hostAttributes(request), token.principal(), request.uri(), restRequestContent(request)); + logger.trace("{}[rest] [authentication_failed]\trealm=[{}], {}, principal=[{}], uri=[{}], request_body=[{}]", getPrefix(), + realm, hostAttributes(request), token.principal(), request.uri(), restRequestContent(request)); } } @@ -235,12 +219,12 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud if ((SystemUser.is(user) && SystemPrivilege.INSTANCE.predicate().test(action)) || XPackUser.is(user)) { if (logger.isTraceEnabled()) { if (indices != null) { - logger.trace("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, indices, + logger.trace("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, indices, message.getClass().getSimpleName()); } else { - logger.trace("{}[transport] [access_granted]\t{}, {}, action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, + logger.trace("{}[transport] [access_granted]\t{}, {}, action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, message.getClass().getSimpleName()); } } @@ -249,20 +233,21 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud if (indices != null) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, indices, + logger.debug("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, indices, message.getClass().getSimpleName()); } else { - logger.info("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, indices); + logger.info("{}[transport] [access_granted]\t{}, {}, action=[{}], indices=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [access_granted]\t{}, {}, action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, message.getClass().getSimpleName()); + logger.debug("{}[transport] [access_granted]\t{}, {}, action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, + message.getClass().getSimpleName()); } else { - logger.info("{}[transport] [access_granted]\t{}, {}, action=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action); + logger.info("{}[transport] [access_granted]\t{}, {}, action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action); } } } @@ -272,20 +257,21 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud String indices = indicesString(message); if (indices != null) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [access_denied]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, indices, + logger.debug("{}[transport] [access_denied]\t{}, {}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, indices, message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [access_denied]\t{}, {}, action=[{}], indices=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, indices); + logger.error("{}[transport] [access_denied]\t{}, {}, action=[{}], indices=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [access_denied]\t{}, {}, action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action, message.getClass().getSimpleName()); + logger.debug("{}[transport] [access_denied]\t{}, {}, action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action, + message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [access_denied]\t{}, {}, action=[{}]", prefix, - originAttributes(message, transport, threadContext), principal(user), action); + logger.error("{}[transport] [access_denied]\t{}, {}, action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), principal(user), action); } } } @@ -293,10 +279,10 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud @Override public void tamperedRequest(RestRequest request) { if (logger.isDebugEnabled()) { - logger.debug("{}[rest] [tampered_request]\t{}, uri=[{}], request_body=[{}]", prefix, hostAttributes(request), request.uri(), - restRequestContent(request)); + logger.debug("{}[rest] [tampered_request]\t{}, uri=[{}], request_body=[{}]", getPrefix(), hostAttributes(request), + request.uri(), restRequestContent(request)); } else { - logger.error("{}[rest] [tampered_request]\t{}, uri=[{}]", prefix, hostAttributes(request), request.uri()); + logger.error("{}[rest] [tampered_request]\t{}, uri=[{}]", getPrefix(), hostAttributes(request), request.uri()); } } @@ -305,19 +291,21 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud String indices = indicesString(message); if (indices != null) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [tampered_request]\t{}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), action, indices, message.getClass().getSimpleName()); + logger.debug("{}[transport] [tampered_request]\t{}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, indices, + message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [tampered_request]\t{}, action=[{}], indices=[{}]", prefix, - originAttributes(message, transport, threadContext), action, indices); + logger.error("{}[transport] [tampered_request]\t{}, action=[{}], indices=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [tampered_request]\t{}, action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), action, message.getClass().getSimpleName()); + logger.debug("{}[transport] [tampered_request]\t{}, action=[{}], request=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action, + message.getClass().getSimpleName()); } else { - logger.error("{}[transport] [tampered_request]\t{}, action=[{}]", prefix, - originAttributes(message, transport, threadContext), action); + logger.error("{}[transport] [tampered_request]\t{}, action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), action); } } } @@ -327,20 +315,21 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud String indices = indicesString(request); if (indices != null) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}], request=[{}]", prefix, - originAttributes(request, transport, threadContext), principal(user), action, indices, + logger.debug("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}], request=[{}]", getPrefix(), + originAttributes(request, clusterService.localNode(), threadContext), principal(user), action, indices, request.getClass().getSimpleName()); } else { - logger.error("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}]", prefix, - originAttributes(request, transport, threadContext), principal(user), action, indices); + logger.error("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}]", getPrefix(), + originAttributes(request, clusterService.localNode(), threadContext), principal(user), action, indices); } } else { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [tampered_request]\t{}, {}, action=[{}], request=[{}]", prefix, - originAttributes(request, transport, threadContext), principal(user), action, request.getClass().getSimpleName()); + logger.debug("{}[transport] [tampered_request]\t{}, {}, action=[{}], request=[{}]", getPrefix(), + originAttributes(request, clusterService.localNode(), threadContext), principal(user), action, + request.getClass().getSimpleName()); } else { - logger.error("{}[transport] [tampered_request]\t{}, {}, action=[{}]", prefix, - originAttributes(request, transport, threadContext), principal(user), action); + logger.error("{}[transport] [tampered_request]\t{}, {}, action=[{}]", getPrefix(), + originAttributes(request, clusterService.localNode(), threadContext), principal(user), action); } } } @@ -348,48 +337,50 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud @Override public void connectionGranted(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { if (logger.isTraceEnabled()) { - logger.trace("{}[ip_filter] [connection_granted]\torigin_address=[{}], transport_profile=[{}], rule=[{}]", prefix, + logger.trace("{}[ip_filter] [connection_granted]\torigin_address=[{}], transport_profile=[{}], rule=[{}]", getPrefix(), NetworkAddress.format(inetAddress), profile, rule); } } @Override public void connectionDenied(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - logger.error("{}[ip_filter] [connection_denied]\torigin_address=[{}], transport_profile=[{}], rule=[{}]", prefix, + logger.error("{}[ip_filter] [connection_denied]\torigin_address=[{}], transport_profile=[{}], rule=[{}]", getPrefix(), NetworkAddress.format(inetAddress), profile, rule); } @Override public void runAsGranted(User user, String action, TransportMessage message) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), user.principal(), user.runAs().principal(), action, - message.getClass().getSimpleName()); + logger.debug("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", + getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), user.principal(), + user.runAs().principal(), action, message.getClass().getSimpleName()); } else { - logger.info("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}]", prefix, - originAttributes(message, transport, threadContext), user.principal(), user.runAs().principal(), action); + logger.info("{}[transport] [run_as_granted]\t{}, principal=[{}], run_as_principal=[{}], action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), user.principal(), + user.runAs().principal(), action); } } @Override public void runAsDenied(User user, String action, TransportMessage message) { if (logger.isDebugEnabled()) { - logger.debug("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", prefix, - originAttributes(message, transport, threadContext), user.principal(), user.runAs().principal(), action, - message.getClass().getSimpleName()); + logger.debug("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}], request=[{}]", + getPrefix(), originAttributes(message, clusterService.localNode(), threadContext), user.principal(), + user.runAs().principal(), action, message.getClass().getSimpleName()); } else { - logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}]", prefix, - originAttributes(message, transport, threadContext), user.principal(), user.runAs().principal(), action); + logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], run_as_principal=[{}], action=[{}]", getPrefix(), + originAttributes(message, clusterService.localNode(), threadContext), user.principal(), + user.runAs().principal(), action); } } @Override public void runAsDenied(User user, RestRequest request) { if (logger.isDebugEnabled()) { - logger.debug("{}[rest] [run_as_denied]\t{}, principal=[{}], uri=[{}], request_body=[{}]", prefix, + logger.debug("{}[rest] [run_as_denied]\t{}, principal=[{}], uri=[{}], request_body=[{}]", getPrefix(), hostAttributes(request), user.principal(), request.uri(), restRequestContent(request)); } else { - logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], uri=[{}]", prefix, + logger.info("{}[transport] [run_as_denied]\t{}, principal=[{}], uri=[{}]", getPrefix(), hostAttributes(request), user.principal(), request.uri()); } } @@ -405,7 +396,7 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud return "origin_address=[" + formattedAddress + "]"; } - static String originAttributes(TransportMessage message, Transport transport, ThreadContext threadContext) { + static String originAttributes(TransportMessage message, DiscoveryNode localNode, ThreadContext threadContext) { StringBuilder builder = new StringBuilder(); // first checking if the message originated in a rest call @@ -433,21 +424,21 @@ public class LoggingAuditTrail extends AbstractLifecycleComponent implements Aud // the call was originated locally on this node return builder.append("origin_type=[local_node], origin_address=[") - .append(transport.boundAddress().publishAddress().getAddress()) + .append(localNode.getHostAddress()) .append("]") .toString(); } - static String resolvePrefix(Settings settings, Transport transport) { + static String resolvePrefix(Settings settings, DiscoveryNode localNode) { StringBuilder builder = new StringBuilder(); if (HOST_ADDRESS_SETTING.get(settings)) { - String address = transport.boundAddress().publishAddress().getAddress(); + String address = localNode.getHostAddress(); if (address != null) { builder.append("[").append(address).append("] "); } } if (HOST_NAME_SETTING.get(settings)) { - String hostName = transport.boundAddress().publishAddress().getHost(); + String hostName = localNode.getHostName(); if (hostName != null) { builder.append("[").append(hostName).append("] "); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Authentication.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Authentication.java index fe83bff0092..be2c54cd975 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Authentication.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Authentication.java @@ -77,6 +77,10 @@ public class Authentication { return deserializeHeaderAndPutInContext(authenticationHeader, ctx, cryptoService, sign); } + public static Authentication getAuthentication(ThreadContext context) { + return context.getTransient(Authentication.AUTHENTICATION_KEY); + } + static Authentication deserializeHeaderAndPutInContext(String header, ThreadContext ctx, CryptoService cryptoService, boolean sign) throws IOException, IllegalArgumentException { assert ctx.getTransient(AUTHENTICATION_KEY) == null; @@ -93,7 +97,7 @@ public class Authentication { return authentication; } - void writeToContextIfMissing(ThreadContext context, CryptoService cryptoService, boolean sign) + public void writeToContextIfMissing(ThreadContext context, CryptoService cryptoService, boolean sign) throws IOException, IllegalArgumentException { if (context.getTransient(AUTHENTICATION_KEY) != null) { if (context.getHeader(AUTHENTICATION_KEY) == null) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationModule.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationModule.java deleted file mode 100644 index afdf6f563c2..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationModule.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.authc; - -import org.elasticsearch.common.inject.multibindings.MapBinder; -import org.elasticsearch.common.inject.util.Providers; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectoryRealm; -import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; -import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; -import org.elasticsearch.xpack.security.authc.file.FileRealm; -import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; -import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; -import org.elasticsearch.xpack.security.authc.pki.PkiRealm; -import org.elasticsearch.xpack.security.support.AbstractSecurityModule; -import org.elasticsearch.xpack.security.user.AnonymousUser; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * - */ -public class AuthenticationModule extends AbstractSecurityModule.Node { - - static final List INTERNAL_REALM_TYPES = - Arrays.asList(ReservedRealm.TYPE, NativeRealm.TYPE, FileRealm.TYPE, ActiveDirectoryRealm.TYPE, LdapRealm.TYPE, PkiRealm.TYPE); - - private final Map>>> customRealms = - new HashMap<>(); - - private Class authcFailureHandler = null; - - public AuthenticationModule(Settings settings) { - super(settings); - } - - @Override - protected void configureNode() { - if (!securityEnabled) { - bind(Realms.class).toProvider(Providers.of(null)); - return; - } - - AnonymousUser.initialize(settings); - MapBinder mapBinder = MapBinder.newMapBinder(binder(), String.class, Realm.Factory.class); - mapBinder.addBinding(FileRealm.TYPE).to(FileRealm.Factory.class).asEagerSingleton(); - mapBinder.addBinding(NativeRealm.TYPE).to(NativeRealm.Factory.class).asEagerSingleton(); - mapBinder.addBinding(ActiveDirectoryRealm.TYPE).to(ActiveDirectoryRealm.Factory.class).asEagerSingleton(); - mapBinder.addBinding(LdapRealm.TYPE).to(LdapRealm.Factory.class).asEagerSingleton(); - mapBinder.addBinding(PkiRealm.TYPE).to(PkiRealm.Factory.class).asEagerSingleton(); - for (Entry>>> entry : - customRealms.entrySet()) { - mapBinder.addBinding(entry.getKey()).to(entry.getValue()).asEagerSingleton(); - } - - bind(ReservedRealm.class).asEagerSingleton(); - bind(Realms.class).asEagerSingleton(); - if (authcFailureHandler == null) { - bind(AuthenticationFailureHandler.class).to(DefaultAuthenticationFailureHandler.class).asEagerSingleton(); - } else { - bind(AuthenticationFailureHandler.class).to(authcFailureHandler).asEagerSingleton(); - } - bind(NativeUsersStore.class).asEagerSingleton(); - bind(AuthenticationService.class).to(InternalAuthenticationService.class).asEagerSingleton(); - } - - /** - * Registers a custom realm type and factory for use as a authentication realm - * - * @param type the type of the realm that identifies it. Must not be null, empty, or the same value as one of the built-in realms - * @param clazz the factory class that is used to create this type of realm. Must not be null - */ - public void addCustomRealm(String type, Class>> clazz) { - if (type == null || type.isEmpty()) { - throw new IllegalArgumentException("type must not be null or empty"); - } else if (clazz == null) { - throw new IllegalArgumentException("realm factory class cannot be null"); - } else if (INTERNAL_REALM_TYPES.contains(type)) { - throw new IllegalArgumentException("cannot redefine the type [" + type + "] with custom realm [" + clazz.getName() + "]"); - } - customRealms.put(type, clazz); - } - - /** - * Sets the {@link AuthenticationFailureHandler} to the specified implementation - */ - public void setAuthenticationFailureHandler(Class clazz) { - this.authcFailureHandler = clazz; - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index bab60b48076..9cc8a86cedb 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -5,17 +5,64 @@ */ package org.elasticsearch.xpack.security.authc; +import java.io.IOException; +import java.util.List; + import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.node.Node; import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; +import org.elasticsearch.xpack.security.user.AnonymousUser; import org.elasticsearch.xpack.security.user.User; +import org.elasticsearch.xpack.security.audit.AuditTrail; +import org.elasticsearch.xpack.security.crypto.CryptoService; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportMessage; -import java.io.IOException; +import static org.elasticsearch.xpack.security.Security.setting; /** - * Responsible for authenticating the Users behind requests + * An authentication service that delegates the authentication process to its configured {@link Realm realms}. + * This service also supports request level caching of authenticated users (i.e. once a user authenticated + * successfully, it is set on the request context to avoid subsequent redundant authentication process) */ -public interface AuthenticationService { +public class AuthenticationService extends AbstractComponent { + + public static final Setting SIGN_USER_HEADER = + Setting.boolSetting(setting("authc.sign_user_header"), true, Property.NodeScope); + public static final Setting RUN_AS_ENABLED = + Setting.boolSetting(setting("authc.run_as.enabled"), true, Property.NodeScope); + public static final String RUN_AS_USER_HEADER = "es-security-runas-user"; + + private final Realms realms; + private final AuditTrail auditTrail; + private final CryptoService cryptoService; + private final AuthenticationFailureHandler failureHandler; + private final ThreadContext threadContext; + private final String nodeName; + private final boolean signUserHeader; + private final boolean runAsEnabled; + + public AuthenticationService(Settings settings, Realms realms, AuditTrailService auditTrail, CryptoService cryptoService, + AuthenticationFailureHandler failureHandler, ThreadPool threadPool) { + super(settings); + this.nodeName = Node.NODE_NAME_SETTING.get(settings); + this.realms = realms; + this.auditTrail = auditTrail; + this.cryptoService = cryptoService; + this.failureHandler = failureHandler; + this.threadContext = threadPool.getThreadContext(); + this.signUserHeader = SIGN_USER_HEADER.get(settings); + this.runAsEnabled = RUN_AS_ENABLED.get(settings); + } /** * Authenticates the user that is associated with the given request. If the user was authenticated successfully (i.e. @@ -28,7 +75,9 @@ public interface AuthenticationService { * user credentials were found to be invalid * @throws IOException If an error occurs when reading or writing */ - Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException; + public Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException { + return createAuthenticator(request).authenticate(); + } /** * Authenticates the user that is associated with the given message. If the user was authenticated successfully (i.e. @@ -47,9 +96,11 @@ public interface AuthenticationService { * * @throws ElasticsearchSecurityException If the associated user credentials were found to be invalid or in the * case where there was no user associated with the request, if the defautl - * token could not be authenticated. + * token could not be authenticated. */ - Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException; + public Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException { + return createAuthenticator(action, message, fallbackUser).authenticate(); + } /** * Checks if there's already a user header attached to the given message. If missing, a new header is @@ -57,7 +108,310 @@ public interface AuthenticationService { * * @param user The user to be attached if the header is missing */ - void attachUserIfMissing(User user) throws IOException, IllegalArgumentException; + public void attachUserIfMissing(User user) throws IOException { + Authentication authentication = new Authentication(user, new RealmRef("__attach", "__attach", nodeName), null); + authentication.writeToContextIfMissing(threadContext, cryptoService, signUserHeader); + } - Authentication getCurrentAuthentication(); + Authenticator createAuthenticator(RestRequest request) { + return new Authenticator(request); + } + + Authenticator createAuthenticator(String action, TransportMessage message, User fallbackUser) { + return new Authenticator(action, message, fallbackUser); + } + + class Authenticator { + + private final AuditableRequest request; + private final User fallbackUser; + + private RealmRef authenticatedBy = null; + private RealmRef lookedupBy = null; + + Authenticator(RestRequest request) { + this.request = new Rest(request); + this.fallbackUser = null; + } + + Authenticator(String action, TransportMessage message, User fallbackUser) { + this.request = new Transport(action, message); + this.fallbackUser = fallbackUser; + } + + Authentication authenticate() throws IOException, IllegalArgumentException { + Authentication existing = getCurrentAuthentication(); + if (existing != null) { + return existing; + } + + AuthenticationToken token = extractToken(); + if (token == null) { + return handleNullToken(); + } + + User user = authenticateToken(token); + if (user == null) { + throw handleNullUser(token); + } + user = lookupRunAsUserIfNecessary(user, token); + + final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy); + authentication.writeToContext(threadContext, cryptoService, signUserHeader); + return authentication; + } + + Authentication getCurrentAuthentication() { + Authentication authentication; + try { + authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader); + } catch (Exception e) { + throw request.tamperedRequest(); + } + + // make sure this isn't a rest request since we don't allow authentication to be read via a HTTP request... + if (authentication != null && request instanceof Rest) { + throw request.tamperedRequest(); + } + return authentication; + } + + AuthenticationToken extractToken() { + AuthenticationToken token = null; + try { + for (Realm realm : realms) { + token = realm.token(threadContext); + if (token != null) { + logger.trace("realm [{}] resolved authentication token [{}] from [{}]", realm, token.principal(), request); + break; + } + } + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("failed to extract token from request: [{}]", e, request); + } else { + logger.warn("failed to extract token from request: [{}]: {}", request, e.getMessage()); + } + throw request.exceptionProcessingRequest(e, null); + } + return token; + } + + Authentication handleNullToken() throws IOException { + Authentication authentication = null; + if (fallbackUser != null) { + RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName); + authentication = new Authentication(fallbackUser, authenticatedBy, null); + } else if (AnonymousUser.enabled()) { + RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName); + authentication = new Authentication(AnonymousUser.INSTANCE, authenticatedBy, null); + } + + if (authentication != null) { + authentication.writeToContext(threadContext, cryptoService, signUserHeader); + return authentication; + } + throw request.anonymousAccessDenied(); + } + + User authenticateToken(AuthenticationToken token) { + User user = null; + try { + for (Realm realm : realms) { + if (realm.supports(token)) { + user = realm.authenticate(token); + if (user != null) { + authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName); + break; + } + request.realmAuthenticationFailed(token, realm.name()); + } + } + } catch (Exception e) { + logger.debug("authentication failed for principal [{}], [{}] ", e, token.principal(), request); + throw request.exceptionProcessingRequest(e, token); + } finally { + token.clearCredentials(); + } + return user; + } + + ElasticsearchSecurityException handleNullUser(AuthenticationToken token) { + throw request.authenticationFailed(token); + } + + boolean shouldTryToRunAs(User authenticatedUser, AuthenticationToken token) { + if (runAsEnabled == false) { + return false; + } + + String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER); + if (runAsUsername == null) { + return false; + } + + if (runAsUsername.isEmpty()) { + logger.debug("user [{}] attempted to runAs with an empty username", authenticatedUser.principal()); + throw request.runAsDenied(new User(authenticatedUser.principal(), authenticatedUser.roles(), + new User(runAsUsername, Strings.EMPTY_ARRAY)), token); + } + return true; + } + + User lookupRunAsUserIfNecessary(User authenticatedUser, AuthenticationToken token) { + User user = authenticatedUser; + if (shouldTryToRunAs(user, token) == false) { + return user; + } + + final String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER); + try { + for (Realm realm : realms) { + if (realm.userLookupSupported()) { + User runAsUser = realm.lookupUser(runAsUsername); + if (runAsUser != null) { + lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName); + user = new User(user.principal(), user.roles(), runAsUser); + return user; + } + } + } + + // the requested run as user does not exist, but we don't throw an error here otherwise this could let + // information leak about users in the system... instead we'll just let the authz service fail throw an + // authorization error + user = new User(user.principal(), user.roles(), new User(runAsUsername, Strings.EMPTY_ARRAY)); + } catch (Exception e) { + logger.debug("run as failed for principal [{}], [{}], run as username [{}]", e, token.principal(), request, runAsUsername); + throw request.exceptionProcessingRequest(e, token); + } + return user; + } + + abstract class AuditableRequest { + + abstract void realmAuthenticationFailed(AuthenticationToken token, String realm); + + abstract ElasticsearchSecurityException tamperedRequest(); + + abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token); + + abstract ElasticsearchSecurityException authenticationFailed(AuthenticationToken token); + + abstract ElasticsearchSecurityException anonymousAccessDenied(); + + abstract ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token); + } + + class Transport extends AuditableRequest { + + private final String action; + private final TransportMessage message; + + Transport(String action, TransportMessage message) { + this.action = action; + this.message = message; + } + + @Override + void realmAuthenticationFailed(AuthenticationToken token, String realm) { + auditTrail.authenticationFailed(realm, token, action, message); + } + + @Override + ElasticsearchSecurityException tamperedRequest() { + auditTrail.tamperedRequest(action, message); + return new ElasticsearchSecurityException("failed to verify signed authentication information"); + } + + @Override + ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token) { + if (token != null) { + auditTrail.authenticationFailed(token, action, message); + } else { + auditTrail.authenticationFailed(action, message); + } + return failureHandler.exceptionProcessingRequest(message, action, e, threadContext); + } + + @Override + ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) { + auditTrail.authenticationFailed(token, action, message); + return failureHandler.failedAuthentication(message, token, action, threadContext); + } + + @Override + ElasticsearchSecurityException anonymousAccessDenied() { + auditTrail.anonymousAccessDenied(action, message); + return failureHandler.missingToken(message, action, threadContext); + } + + @Override + ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) { + auditTrail.runAsDenied(user, action, message); + return failureHandler.failedAuthentication(message, token, action, threadContext); + } + + public String toString() { + return "transport request action [" + action + "]"; + } + } + + class Rest extends AuditableRequest { + + private final RestRequest request; + + Rest(RestRequest request) { + this.request = request; + } + + @Override + void realmAuthenticationFailed(AuthenticationToken token, String realm) { + auditTrail.authenticationFailed(realm, token, request); + } + + @Override + ElasticsearchSecurityException tamperedRequest() { + auditTrail.tamperedRequest(request); + return new ElasticsearchSecurityException("rest request attempted to inject a user"); + } + + @Override + ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token) { + if (token != null) { + auditTrail.authenticationFailed(token, request); + } else { + auditTrail.authenticationFailed(request); + } + return failureHandler.exceptionProcessingRequest(request, e, threadContext); + } + + @Override + ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) { + auditTrail.authenticationFailed(token, request); + return failureHandler.failedAuthentication(request, token, threadContext); + } + + @Override + ElasticsearchSecurityException anonymousAccessDenied() { + auditTrail.anonymousAccessDenied(request); + return failureHandler.missingToken(request, threadContext); + } + + @Override + ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) { + auditTrail.runAsDenied(user, request); + return failureHandler.failedAuthentication(request, token, threadContext); + } + + public String toString() { + return "rest request uri [" + request.uri() + "]"; + } + } + } + + public static void addSettings(List> settings) { + settings.add(SIGN_USER_HEADER); + settings.add(RUN_AS_ENABLED); + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalAuthenticationService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalAuthenticationService.java deleted file mode 100644 index a18f50b9747..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalAuthenticationService.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.authc; - -import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.rest.RestController; -import org.elasticsearch.node.Node; -import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; -import org.elasticsearch.xpack.security.user.AnonymousUser; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.crypto.CryptoService; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportMessage; - -import java.io.IOException; -import java.util.List; - -import static org.elasticsearch.xpack.security.Security.setting; - -/** - * An authentication service that delegates the authentication process to its configured {@link Realm realms}. - * This service also supports request level caching of authenticated users (i.e. once a user authenticated - * successfully, it is set on the request context to avoid subsequent redundant authentication process) - */ -public class InternalAuthenticationService extends AbstractComponent implements AuthenticationService { - - public static final Setting SIGN_USER_HEADER = - Setting.boolSetting(setting("authc.sign_user_header"), true, Property.NodeScope); - public static final Setting RUN_AS_ENABLED = - Setting.boolSetting(setting("authc.run_as.enabled"), true, Property.NodeScope); - public static final String RUN_AS_USER_HEADER = "es-security-runas-user"; - - private final Realms realms; - private final AuditTrail auditTrail; - private final CryptoService cryptoService; - private final AuthenticationFailureHandler failureHandler; - private final ThreadContext threadContext; - private final String nodeName; - private final boolean signUserHeader; - private final boolean runAsEnabled; - - @Inject - public InternalAuthenticationService(Settings settings, Realms realms, AuditTrail auditTrail, CryptoService cryptoService, - AuthenticationFailureHandler failureHandler, ThreadPool threadPool, RestController controller) { - super(settings); - this.nodeName = Node.NODE_NAME_SETTING.get(settings); - this.realms = realms; - this.auditTrail = auditTrail; - this.cryptoService = cryptoService; - this.failureHandler = failureHandler; - this.threadContext = threadPool.getThreadContext(); - this.signUserHeader = SIGN_USER_HEADER.get(settings); - this.runAsEnabled = RUN_AS_ENABLED.get(settings); - if (runAsEnabled) { - controller.registerRelevantHeaders(RUN_AS_USER_HEADER); - } - } - - @Override - public Authentication authenticate(RestRequest request) throws IOException, ElasticsearchSecurityException { - return createAuthenticator(request).authenticate(); - } - - @Override - public Authentication authenticate(String action, TransportMessage message, User fallbackUser) throws IOException { - return createAuthenticator(action, message, fallbackUser).authenticate(); - } - - @Override - public void attachUserIfMissing(User user) throws IOException { - Authentication authentication = new Authentication(user, new RealmRef("__attach", "__attach", nodeName), null); - authentication.writeToContextIfMissing(threadContext, cryptoService, signUserHeader); - } - - @Override - public Authentication getCurrentAuthentication() { - try { - Authentication authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader); - return authentication == null ? null : authentication; - } catch (IOException e) { - logger.error("failed to read authentication", e); - return null; - } - } - - Authenticator createAuthenticator(RestRequest request) { - return new Authenticator(request); - } - - Authenticator createAuthenticator(String action, TransportMessage message, User fallbackUser) { - return new Authenticator(action, message, fallbackUser); - } - - class Authenticator { - - private final AuditableRequest request; - private final User fallbackUser; - - private RealmRef authenticatedBy = null; - private RealmRef lookedupBy = null; - - Authenticator(RestRequest request) { - this.request = new Rest(request); - this.fallbackUser = null; - } - - Authenticator(String action, TransportMessage message, User fallbackUser) { - this.request = new Transport(action, message); - this.fallbackUser = fallbackUser; - } - - Authentication authenticate() throws IOException, IllegalArgumentException { - Authentication existing = getCurrentAuthentication(); - if (existing != null) { - return existing; - } - - AuthenticationToken token = extractToken(); - if (token == null) { - return handleNullToken(); - } - - User user = authenticateToken(token); - if (user == null) { - throw handleNullUser(token); - } - user = lookupRunAsUserIfNecessary(user, token); - - final Authentication authentication = new Authentication(user, authenticatedBy, lookedupBy); - authentication.writeToContext(threadContext, cryptoService, signUserHeader); - return authentication; - } - - Authentication getCurrentAuthentication() { - Authentication authentication; - try { - authentication = Authentication.readFromContext(threadContext, cryptoService, signUserHeader); - } catch (Exception e) { - throw request.tamperedRequest(); - } - - // make sure this isn't a rest request since we don't allow authentication to be read via a HTTP request... - if (authentication != null && request instanceof Rest) { - throw request.tamperedRequest(); - } - return authentication; - } - - AuthenticationToken extractToken() { - AuthenticationToken token = null; - try { - for (Realm realm : realms) { - token = realm.token(threadContext); - if (token != null) { - logger.trace("realm [{}] resolved authentication token [{}] from [{}]", realm, token.principal(), request); - break; - } - } - } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug("failed to extract token from request: [{}]", e, request); - } else { - logger.warn("failed to extract token from request: [{}]: {}", request, e.getMessage()); - } - throw request.exceptionProcessingRequest(e, null); - } - return token; - } - - Authentication handleNullToken() throws IOException { - Authentication authentication = null; - if (fallbackUser != null) { - RealmRef authenticatedBy = new RealmRef("__fallback", "__fallback", nodeName); - authentication = new Authentication(fallbackUser, authenticatedBy, null); - } else if (AnonymousUser.enabled()) { - RealmRef authenticatedBy = new RealmRef("__anonymous", "__anonymous", nodeName); - authentication = new Authentication(AnonymousUser.INSTANCE, authenticatedBy, null); - } - - if (authentication != null) { - authentication.writeToContext(threadContext, cryptoService, signUserHeader); - return authentication; - } - throw request.anonymousAccessDenied(); - } - - User authenticateToken(AuthenticationToken token) { - User user = null; - try { - for (Realm realm : realms) { - if (realm.supports(token)) { - user = realm.authenticate(token); - if (user != null) { - authenticatedBy = new RealmRef(realm.name(), realm.type(), nodeName); - break; - } - request.realmAuthenticationFailed(token, realm.name()); - } - } - } catch (Exception e) { - logger.debug("authentication failed for principal [{}], [{}] ", e, token.principal(), request); - throw request.exceptionProcessingRequest(e, token); - } finally { - token.clearCredentials(); - } - return user; - } - - ElasticsearchSecurityException handleNullUser(AuthenticationToken token) { - throw request.authenticationFailed(token); - } - - boolean shouldTryToRunAs(User authenticatedUser, AuthenticationToken token) { - if (runAsEnabled == false) { - return false; - } - - String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER); - if (runAsUsername == null) { - return false; - } - - if (runAsUsername.isEmpty()) { - logger.debug("user [{}] attempted to runAs with an empty username", authenticatedUser.principal()); - throw request.runAsDenied(new User(authenticatedUser.principal(), authenticatedUser.roles(), - new User(runAsUsername, Strings.EMPTY_ARRAY)), token); - } - return true; - } - - User lookupRunAsUserIfNecessary(User authenticatedUser, AuthenticationToken token) { - User user = authenticatedUser; - if (shouldTryToRunAs(user, token) == false) { - return user; - } - - final String runAsUsername = threadContext.getHeader(RUN_AS_USER_HEADER); - try { - for (Realm realm : realms) { - if (realm.userLookupSupported()) { - User runAsUser = realm.lookupUser(runAsUsername); - if (runAsUser != null) { - lookedupBy = new RealmRef(realm.name(), realm.type(), nodeName); - user = new User(user.principal(), user.roles(), runAsUser); - return user; - } - } - } - - // the requested run as user does not exist, but we don't throw an error here otherwise this could let - // information leak about users in the system... instead we'll just let the authz service fail throw an - // authorization error - user = new User(user.principal(), user.roles(), new User(runAsUsername, Strings.EMPTY_ARRAY)); - } catch (Exception e) { - logger.debug("run as failed for principal [{}], [{}], run as username [{}]", e, token.principal(), request, runAsUsername); - throw request.exceptionProcessingRequest(e, token); - } - return user; - } - - abstract class AuditableRequest { - - abstract void realmAuthenticationFailed(AuthenticationToken token, String realm); - - abstract ElasticsearchSecurityException tamperedRequest(); - - abstract ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token); - - abstract ElasticsearchSecurityException authenticationFailed(AuthenticationToken token); - - abstract ElasticsearchSecurityException anonymousAccessDenied(); - - abstract ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token); - } - - class Transport extends AuditableRequest { - - private final String action; - private final TransportMessage message; - - Transport(String action, TransportMessage message) { - this.action = action; - this.message = message; - } - - @Override - void realmAuthenticationFailed(AuthenticationToken token, String realm) { - auditTrail.authenticationFailed(realm, token, action, message); - } - - @Override - ElasticsearchSecurityException tamperedRequest() { - auditTrail.tamperedRequest(action, message); - return new ElasticsearchSecurityException("failed to verify signed authentication information"); - } - - @Override - ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token) { - if (token != null) { - auditTrail.authenticationFailed(token, action, message); - } else { - auditTrail.authenticationFailed(action, message); - } - return failureHandler.exceptionProcessingRequest(message, action, e, threadContext); - } - - @Override - ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) { - auditTrail.authenticationFailed(token, action, message); - return failureHandler.failedAuthentication(message, token, action, threadContext); - } - - @Override - ElasticsearchSecurityException anonymousAccessDenied() { - auditTrail.anonymousAccessDenied(action, message); - return failureHandler.missingToken(message, action, threadContext); - } - - @Override - ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) { - auditTrail.runAsDenied(user, action, message); - return failureHandler.failedAuthentication(message, token, action, threadContext); - } - - public String toString() { - return "transport request action [" + action + "]"; - } - } - - class Rest extends AuditableRequest { - - private final RestRequest request; - - Rest(RestRequest request) { - this.request = request; - } - - @Override - void realmAuthenticationFailed(AuthenticationToken token, String realm) { - auditTrail.authenticationFailed(realm, token, request); - } - - @Override - ElasticsearchSecurityException tamperedRequest() { - auditTrail.tamperedRequest(request); - return new ElasticsearchSecurityException("rest request attempted to inject a user"); - } - - @Override - ElasticsearchSecurityException exceptionProcessingRequest(Exception e, @Nullable AuthenticationToken token) { - if (token != null) { - auditTrail.authenticationFailed(token, request); - } else { - auditTrail.authenticationFailed(request); - } - return failureHandler.exceptionProcessingRequest(request, e, threadContext); - } - - @Override - ElasticsearchSecurityException authenticationFailed(AuthenticationToken token) { - auditTrail.authenticationFailed(token, request); - return failureHandler.failedAuthentication(request, token, threadContext); - } - - @Override - ElasticsearchSecurityException anonymousAccessDenied() { - auditTrail.anonymousAccessDenied(request); - return failureHandler.missingToken(request, threadContext); - } - - @Override - ElasticsearchSecurityException runAsDenied(User user, AuthenticationToken token) { - auditTrail.runAsDenied(user, request); - return failureHandler.failedAuthentication(request, token, threadContext); - } - - public String toString() { - return "rest request uri [" + request.uri() + "]"; - } - } - } - - public static void addSettings(List> settings) { - settings.add(SIGN_USER_HEADER); - settings.add(RUN_AS_ENABLED); - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realm.java index dd5a9f404c3..078ff26c76e 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realm.java @@ -17,7 +17,7 @@ import java.util.Map; * service } delegates the authentication process. Different realms may be defined, each may be based on different * authentication mechanism supporting its own specific authentication token type. */ -public abstract class Realm implements Comparable { +public abstract class Realm implements Comparable { protected final ESLogger logger; protected final String type; @@ -67,7 +67,7 @@ public abstract class Realm implements Comparable * @param context The context that will provide information about the incoming request * @return The authentication token or {@code null} if not found */ - public abstract T token(ThreadContext context); + public abstract AuthenticationToken token(ThreadContext context); /** * Authenticates the given token. A successful authentication will return the User associated @@ -76,7 +76,7 @@ public abstract class Realm implements Comparable * @param token The authentication token * @return The authenticated user or {@code null} if authentication failed. */ - public abstract User authenticate(T token); + public abstract User authenticate(AuthenticationToken token); /** * Looks up the user identified the String identifier. A successful lookup will return the {@link User} identified @@ -107,44 +107,14 @@ public abstract class Realm implements Comparable } /** - * A factory for a specific realm type. Knows how to create a new realm given the appropriate - * settings. The factory will be called when creating a realm during the parsing of realms defined in the - * elasticsearch.yml file + * A factory interface to construct a security realm. */ - public abstract static class Factory { - - private final String type; - private final boolean internal; - - public Factory(String type, boolean internal) { - this.type = type; - this.internal = internal; - } + public interface Factory { /** - * @return The type of the ream this factory creates + * Constructs a realm which will be used for authentication. + * @param config The configuration for the realm */ - public String type() { - return type; - } - - public boolean internal() { - return internal; - } - - /** - * Creates a new realm based on the given settigns. - * - * @param config The configuration for the realm - * @return The new realm (this method never returns {@code null}). - */ - public abstract R create(RealmConfig config); - - /** - * Creates a default realm, one that has no custom settings. Some realms might require minimal - * settings, in which case, this method will return {@code null}. - */ - public abstract R createDefault(String name); + Realm create(RealmConfig config); } - } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java index 470c664f2e1..0fee39a0e49 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java @@ -5,19 +5,6 @@ */ package org.elasticsearch.xpack.security.authc; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.component.AbstractLifecycleComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.SecurityLicenseState.EnabledRealmType; -import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; -import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; -import org.elasticsearch.xpack.security.authc.file.FileRealm; -import org.elasticsearch.xpack.security.SecurityLicenseState; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,6 +14,21 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.license.XPackLicenseState.AllowedRealmType; +import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectoryRealm; +import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; +import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.authc.file.FileRealm; +import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; +import org.elasticsearch.xpack.security.authc.pki.PkiRealm; + import static org.elasticsearch.xpack.security.Security.setting; /** @@ -34,11 +36,14 @@ import static org.elasticsearch.xpack.security.Security.setting; */ public class Realms extends AbstractLifecycleComponent implements Iterable { + static final List INTERNAL_REALM_TYPES = + Arrays.asList(ReservedRealm.TYPE, NativeRealm.TYPE, FileRealm.TYPE, ActiveDirectoryRealm.TYPE, LdapRealm.TYPE, PkiRealm.TYPE); + public static final Setting REALMS_GROUPS_SETTINGS = Setting.groupSetting(setting("authc.realms."), Property.NodeScope); private final Environment env; private final Map factories; - private final SecurityLicenseState securityLicenseState; + private final XPackLicenseState licenseState; private final ReservedRealm reservedRealm; protected List realms = Collections.emptyList(); @@ -47,13 +52,12 @@ public class Realms extends AbstractLifecycleComponent implements Iterable nativeRealmsOnly = Collections.emptyList(); - @Inject - public Realms(Settings settings, Environment env, Map factories, SecurityLicenseState securityLicenseState, + public Realms(Settings settings, Environment env, Map factories, XPackLicenseState licenseState, ReservedRealm reservedRealm) { super(settings); this.env = env; this.factories = factories; - this.securityLicenseState = securityLicenseState; + this.licenseState = licenseState; this.reservedRealm = reservedRealm; } @@ -67,7 +71,7 @@ public class Realms extends AbstractLifecycleComponent implements Iterable nativeRealms = new ArrayList<>(); for (Realm realm : realms) { // don't add the reserved realm here otherwise we end up with only this realm... - if (AuthenticationModule.INTERNAL_REALM_TYPES.contains(realm.type()) && ReservedRealm.TYPE.equals(realm.type()) == false) { + if (INTERNAL_REALM_TYPES.contains(realm.type()) && ReservedRealm.TYPE.equals(realm.type()) == false) { internalRealms.add(realm); } @@ -100,12 +104,12 @@ public class Realms extends AbstractLifecycleComponent implements Iterable iterator() { - if (securityLicenseState.authenticationAndAuthorizationEnabled() == false) { + if (licenseState.isAuthAllowed() == false) { return Collections.emptyIterator(); } - EnabledRealmType enabledRealmType = securityLicenseState.enabledRealmType(); - switch (enabledRealmType) { + AllowedRealmType allowedRealmType = licenseState.allowedRealmType(); + switch (allowedRealmType) { case ALL: return realms.iterator(); case DEFAULT: @@ -151,7 +155,7 @@ public class Realms extends AbstractLifecycleComponent implements Iterable realms) { Realm.Factory fileRealm = factories.get(FileRealm.TYPE); if (fileRealm != null) { - realms.add(fileRealm.createDefault("default_" + FileRealm.TYPE)); + + realms.add(fileRealm.create(new RealmConfig("default_" + FileRealm.TYPE, Settings.EMPTY, settings, env))); } Realm.Factory indexRealmFactory = factories.get(NativeRealm.TYPE); if (indexRealmFactory != null) { - realms.add(indexRealmFactory.createDefault("default_" + NativeRealm.TYPE)); + realms.add(indexRealmFactory.create(new RealmConfig("default_" + NativeRealm.TYPE, Settings.EMPTY, settings, env))); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java index 812a16ece33..8fff603b1b8 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolver.java @@ -13,15 +13,15 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; -import org.elasticsearch.xpack.security.support.Exceptions; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; @@ -41,9 +41,14 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { this.scope = LdapSearchScope.resolve(settings.get("scope"), LdapSearchScope.SUB_TREE); } - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { + @Override + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) { Filter groupSearchFilter = buildGroupQuery(connection, userDn, timeout, logger); logger.debug("group SID to DN search filter: [{}]", groupSearchFilter); + if (groupSearchFilter == null) { + return Collections.emptyList(); + } SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), groupSearchFilter, SearchRequest.NO_ATTRIBUTES); searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); @@ -51,7 +56,8 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { try { results = search(connection, searchRequest, logger); } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to fetch AD groups for DN [{}]", e, userDn); + logger.error("failed to fetch AD groups for DN [{}]", e, userDn); + return Collections.emptyList(); } List groupList = new ArrayList<>(); @@ -64,11 +70,20 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { return groupList; } + @Override + public String[] attributes() { + // we have to return null since the tokenGroups attribute is computed and can only be retrieved using a BASE level search + return null; + } + static Filter buildGroupQuery(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { try { SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, "tokenGroups"); request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); SearchResultEntry entry = searchForEntry(connection, request, logger); + if (entry == null) { + return null; + } Attribute attribute = entry.getAttribute("tokenGroups"); byte[][] tokenGroupSIDBytes = attribute.getValueByteArrays(); List orFilters = new ArrayList<>(tokenGroupSIDBytes.length); @@ -77,7 +92,8 @@ public class ActiveDirectoryGroupsResolver implements GroupsResolver { } return Filter.createORFilter(orFilters); } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to fetch AD groups for DN [{}]", e, userDn); + logger.error("failed to fetch AD groups for DN [{}]", e, userDn); + return null; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java index e32d5124f9c..91ac8cd2993 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java @@ -5,13 +5,12 @@ */ package org.elasticsearch.xpack.security.authc.activedirectory; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.rest.RestController; +import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.AbstractLdapRealm; +import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.ssl.ClientSSLService; -import org.elasticsearch.watcher.ResourceWatcherService; /** * @@ -20,30 +19,13 @@ public class ActiveDirectoryRealm extends AbstractLdapRealm { public static final String TYPE = "active_directory"; - public ActiveDirectoryRealm(RealmConfig config, - ActiveDirectorySessionFactory connectionFactory, - DnRoleMapper roleMapper) { - - super(TYPE, config, connectionFactory, roleMapper); + public ActiveDirectoryRealm(RealmConfig config, ResourceWatcherService watcherService, ClientSSLService clientSSLService) { + this(config, new ActiveDirectorySessionFactory(config, clientSSLService), + new DnRoleMapper(TYPE, config, watcherService, null)); } - public static class Factory extends AbstractLdapRealm.Factory { - - private final ResourceWatcherService watcherService; - private final ClientSSLService clientSSLService; - - @Inject - public Factory(ResourceWatcherService watcherService, ClientSSLService clientSSLService, RestController restController) { - super(ActiveDirectoryRealm.TYPE, restController); - this.watcherService = watcherService; - this.clientSSLService = clientSSLService; - } - - @Override - public ActiveDirectoryRealm create(RealmConfig config) { - ActiveDirectorySessionFactory connectionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); - DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); - return new ActiveDirectoryRealm(config, connectionFactory, roleMapper); - } + // pkg private for tests + ActiveDirectoryRealm(RealmConfig config, SessionFactory sessionFactory, DnRoleMapper roleMapper) { + super(TYPE, config, sessionFactory, roleMapper); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java index 365724ee2f0..b0231319eb9 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactory.java @@ -5,12 +5,18 @@ */ package org.elasticsearch.xpack.security.authc.activedirectory; +import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPConnectionOptions; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; -import org.elasticsearch.common.Strings; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.cache.Cache; +import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession; @@ -19,11 +25,11 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; -import java.io.IOException; +import java.util.concurrent.ExecutionException; +import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.createFilter; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.search; -import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError; /** * This Class creates LdapSessions authenticating via the custom Active Directory protocol. (that being @@ -40,12 +46,13 @@ public class ActiveDirectorySessionFactory extends SessionFactory { public static final String AD_USER_SEARCH_BASEDN_SETTING = "user_search.base_dn"; public static final String AD_USER_SEARCH_FILTER_SETTING = "user_search.filter"; public static final String AD_USER_SEARCH_SCOPE_SETTING = "user_search.scope"; + private static final String NETBIOS_NAME_FILTER_TEMPLATE = "(netbiosname={0})"; - private final String userSearchDN; private final String domainName; - private final String userSearchFilter; - private final LdapSearchScope userSearchScope; private final GroupsResolver groupResolver; + private final DefaultADAuthenticator defaultADAuthenticator; + private final DownLevelADAuthenticator downLevelADAuthenticator; + private final UpnADAuthenticator upnADAuthenticator; public ActiveDirectorySessionFactory(RealmConfig config, ClientSSLService sslService) { super(config, sslService); @@ -55,63 +62,227 @@ public class ActiveDirectorySessionFactory extends SessionFactory { throw new IllegalArgumentException("missing [" + AD_DOMAIN_NAME_SETTING + "] setting for active directory"); } String domainDN = buildDnFromDomain(domainName); - userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN); - userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE); - userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" + - "(userPrincipalName={0}@" + domainName + ")))"); groupResolver = new ActiveDirectoryGroupsResolver(settings.getAsSettings("group_search"), domainDN); + defaultADAuthenticator = new DefaultADAuthenticator(settings, timeout, logger, groupResolver, domainDN); + downLevelADAuthenticator = new DownLevelADAuthenticator(settings, timeout, logger, groupResolver, domainDN); + upnADAuthenticator = new UpnADAuthenticator(settings, timeout, logger, groupResolver, domainDN); } - @Override - protected LDAPServers ldapServers(Settings settings) { - String[] ldapUrls = settings.getAsArray(URLS_SETTING, new String[]{"ldap://" + domainName + ":389"}); - return new LDAPServers(ldapUrls); + protected String[] getDefaultLdapUrls(Settings settings) { + return new String[] {"ldap://" + settings.get(AD_DOMAIN_NAME_SETTING) + ":389"}; } /** * This is an active directory bind that looks up the user DN after binding with a windows principal. * - * @param userName name of the windows user without the domain - * @return An authenticated + * @param username name of the windows user without the domain + * @return An authenticated LdapSession */ @Override - protected LdapSession getSession(String userName, SecuredString password) throws Exception { - LDAPConnection connection; - - try { - connection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("failed to connect to any active directory servers", e); - } - - String userPrincipal = userName + "@" + domainName; - try { - connection.bind(userPrincipal, new String(password.internalChars())); - SearchRequest searchRequest = new SearchRequest(userSearchDN, userSearchScope.scope(), - createFilter(userSearchFilter, userName), SearchRequest.NO_ATTRIBUTES); - searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResult results = search(connection, searchRequest, logger); - int numResults = results.getEntryCount(); - if (numResults > 1) { - throw new IllegalStateException("search for user [" + userName + "] by principle name yielded multiple results"); - } else if (numResults < 1) { - throw new IllegalStateException("search for user [" + userName + "] by principle name yielded no results"); - } - String dn = results.getSearchEntries().get(0).getDN(); - return new LdapSession(connectionLogger, connection, dn, groupResolver, timeout); - } catch (LDAPException e) { - connection.close(); - throw authenticationError("unable to authenticate user [{}] to active directory domain [{}]", e, userName, domainName); - } + protected LdapSession getSession(String username, SecuredString password) throws Exception { + LDAPConnection connection = serverSet.getConnection(); + ADAuthenticator authenticator = getADAuthenticator(username); + return authenticator.authenticate(connection, username, password); } /** * @param domain active directory domain name * @return LDAP DN, distinguished name, of the root of the domain */ - String buildDnFromDomain(String domain) { + static String buildDnFromDomain(String domain) { return "DC=" + domain.replace(".", ",DC="); } + ADAuthenticator getADAuthenticator(String username) { + if (username.indexOf('\\') > 0) { + return downLevelADAuthenticator; + } else if (username.indexOf("@") > 0) { + return upnADAuthenticator; + } + return defaultADAuthenticator; + } + + abstract static class ADAuthenticator { + + final TimeValue timeout; + final ESLogger logger; + final GroupsResolver groupsResolver; + final String userSearchDN; + final LdapSearchScope userSearchScope; + + ADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + this.timeout = timeout; + this.logger = logger; + this.groupsResolver = groupsResolver; + userSearchDN = settings.get(AD_USER_SEARCH_BASEDN_SETTING, domainDN); + userSearchScope = LdapSearchScope.resolve(settings.get(AD_USER_SEARCH_SCOPE_SETTING), LdapSearchScope.SUB_TREE); + } + + LdapSession authenticate(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + boolean success = false; + try { + connection.bind(bindUsername(username), new String(password.internalChars())); + SearchRequest searchRequest = getSearchRequest(connection, username, password); + searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResult results = search(connection, searchRequest, logger); + int numResults = results.getEntryCount(); + if (numResults > 1) { + throw new IllegalStateException("search for user [" + username + "] by principle name yielded multiple results"); + } else if (numResults < 1) { + throw new IllegalStateException("search for user [" + username + "] by principle name yielded no results"); + } + + String dn = results.getSearchEntries().get(0).getDN(); + LdapSession session = new LdapSession(logger, connection, dn, groupsResolver, timeout, null); + success = true; + return session; + } finally { + if (success == false) { + connection.close(); + } + } + } + + String bindUsername(String username) { + return username; + } + + abstract SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException; + } + + /** + * This authenticator is used for usernames that do not contain an `@` or `/`. It attempts a bind with the provided username combined + * with the domain name specified in settings. On AD DS this will work for both upn@domain and samaccountname@domain; AD LDS will only + * support the upn format + */ + static class DefaultADAuthenticator extends ADAuthenticator { + + final String userSearchFilter; + + final String domainName; + DefaultADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + super(settings, timeout, logger, groupsResolver, domainDN); + domainName = settings.get(AD_DOMAIN_NAME_SETTING); + userSearchFilter = settings.get(AD_USER_SEARCH_FILTER_SETTING, "(&(objectClass=user)(|(sAMAccountName={0})" + + "(userPrincipalName={0}@" + domainName + ")))"); + } + + @Override + SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + return new SearchRequest(userSearchDN, userSearchScope.scope(), + createFilter(userSearchFilter, username), attributesToSearchFor(groupsResolver.attributes())); + } + + @Override + String bindUsername(String username) { + return username + "@" + domainName; + } + } + + /** + * Active Directory calls the format DOMAIN\\username down-level credentials and this class contains the logic necessary + * to authenticate this form of a username + */ + static class DownLevelADAuthenticator extends ADAuthenticator { + Cache domainNameCache = CacheBuilder.builder().setMaximumWeight(100).build(); + + final String domainDN; + final Settings settings; + + DownLevelADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + super(settings, timeout, logger, groupsResolver, domainDN); + this.domainDN = domainDN; + this.settings = settings; + } + + SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + String[] parts = username.split("\\\\"); + assert parts.length == 2; + final String netBiosDomainName = parts[0]; + final String accountName = parts[1]; + + final String domainDn = netBiosDomainNameToDn(connection, netBiosDomainName, username, password); + + return new SearchRequest(domainDn, LdapSearchScope.SUB_TREE.scope(), + createFilter("(&(objectClass=user)(sAMAccountName={0}))", accountName), + attributesToSearchFor(groupsResolver.attributes())); + } + + String netBiosDomainNameToDn(LDAPConnection connection, String netBiosDomainName, String username, SecuredString password) + throws LDAPException { + try { + return domainNameCache.computeIfAbsent(netBiosDomainName, (key) -> { + LDAPConnection searchConnection = connection; + boolean openedConnection = false; + try { + // global catalog does not replicate the necessary information by default + // TODO add settings for ports and maybe cache connectionOptions + if (usingGlobalCatalog(settings, connection)) { + LDAPConnectionOptions options = connectionOptions(settings); + if (connection.getSSLSession() != null) { + searchConnection = new LDAPConnection(connection.getSocketFactory(), options, + connection.getConnectedAddress(), 636); + } else { + searchConnection = new LDAPConnection(options, connection.getConnectedAddress(), 389); + } + openedConnection = true; + searchConnection.bind(username, new String(password.internalChars())); + } + + SearchRequest searchRequest = new SearchRequest(domainDN, LdapSearchScope.SUB_TREE.scope(), + createFilter(NETBIOS_NAME_FILTER_TEMPLATE, netBiosDomainName), "ncname"); + SearchResult results = search(searchConnection, searchRequest, logger); + if (results.getEntryCount() > 0) { + Attribute attribute = results.getSearchEntries().get(0).getAttribute("ncname"); + if (attribute != null) { + return attribute.getValue(); + } + } + logger.debug("failed to find domain name DN from netbios name [{}]", netBiosDomainName); + return null; + } finally { + if (openedConnection) { + searchConnection.close(); + } + } + }); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof LDAPException) { + throw (LDAPException) cause; + } else { + connection.close(); + throw new ElasticsearchException("error occurred while mapping [{}] to domain DN", cause, netBiosDomainName); + } + } + } + + static boolean usingGlobalCatalog(Settings settings, LDAPConnection ldapConnection) { + Boolean usingGlobalCatalog = settings.getAsBoolean("global_catalog", null); + if (usingGlobalCatalog != null) { + return usingGlobalCatalog; + } + return ldapConnection.getConnectedPort() == 3268 || ldapConnection.getConnectedPort() == 3269; + } + } + + static class UpnADAuthenticator extends ADAuthenticator { + + private static final String UPN_USER_FILTER = "(&(objectClass=user)(|(sAMAccountName={0})(userPrincipalName={1})))"; + + UpnADAuthenticator(Settings settings, TimeValue timeout, ESLogger logger, GroupsResolver groupsResolver, String domainDN) { + super(settings, timeout, logger, groupsResolver, domainDN); + } + + SearchRequest getSearchRequest(LDAPConnection connection, String username, SecuredString password) throws LDAPException { + String[] parts = username.split("@"); + assert parts.length == 2; + final String accountName = parts[0]; + final String domainName = parts[1]; + final String domainDN = buildDnFromDomain(domainName); + return new SearchRequest(domainDN, LdapSearchScope.SUB_TREE.scope(), + createFilter(UPN_USER_FILTER, accountName, username), attributesToSearchFor(groupsResolver.attributes())); + } + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java index 219e020b3f4..d100396bf47 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateTool.java @@ -151,8 +151,7 @@ public class ESNativeRealmMigrateTool extends MultiCommand { if ("https".equalsIgnoreCase(uri.getScheme())) { Settings sslSettings = settings.getByPrefix(setting("http.ssl.")); SSLConfiguration.Global globalConfig = new SSLConfiguration.Global(settings); - final ClientSSLService sslService = new ClientSSLService(sslSettings, globalConfig); - sslService.setEnvironment(env); + final ClientSSLService sslService = new ClientSSLService(sslSettings, env, globalConfig, null); final HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection(); AccessController.doPrivileged(new PrivilegedAction() { @Override diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java index be8b2a65250..e018c5e3ac8 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealm.java @@ -5,19 +5,12 @@ */ package org.elasticsearch.xpack.security.authc.esnative; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.authc.Realm; +import java.util.List; + import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; - -import java.util.List; +import org.elasticsearch.xpack.security.user.User; /** * User/password realm that is backed by an Elasticsearch index @@ -58,30 +51,4 @@ public class NativeRealm extends CachingUsernamePasswordRealm { } } } - - public static class Factory extends Realm.Factory { - - private final Settings settings; - private final Environment env; - private final NativeUsersStore userStore; - - @Inject - public Factory(Settings settings, Environment env, NativeUsersStore userStore) { - super(TYPE, true); - this.settings = settings; - this.env = env; - this.userStore = userStore; - } - - @Override - public NativeRealm create(RealmConfig config) { - return new NativeRealm(config, userStore); - } - - @Override - public NativeRealm createDefault(String name) { - RealmConfig config = new RealmConfig(name, Settings.EMPTY, settings, env); - return create(config); - } - } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index c4b5c53a6f5..1134d0d10a8 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -35,8 +35,6 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Provider; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; @@ -58,11 +56,11 @@ import org.elasticsearch.xpack.security.action.user.PutUserRequest; import org.elasticsearch.xpack.security.authc.support.Hasher; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.client.SecurityClient; -import org.elasticsearch.xpack.security.support.SelfReschedulingRunnable; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User.Fields; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.threadpool.ThreadPool.Cancellable; import org.elasticsearch.threadpool.ThreadPool.Names; import java.util.ArrayList; @@ -114,20 +112,18 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL private final Hasher hasher = Hasher.BCRYPT; private final List listeners = new CopyOnWriteArrayList<>(); private final AtomicReference state = new AtomicReference<>(State.INITIALIZED); - private final Provider clientProvider; + private final InternalClient client; private final ThreadPool threadPool; - private SelfReschedulingRunnable userPoller; - private Client client; + private Cancellable pollerCancellable; private int scrollSize; private TimeValue scrollKeepAlive; private volatile boolean securityIndexExists = false; - @Inject - public NativeUsersStore(Settings settings, Provider clientProvider, ThreadPool threadPool) { + public NativeUsersStore(Settings settings, InternalClient client, ThreadPool threadPool) { super(settings); - this.clientProvider = clientProvider; + this.client = client; this.threadPool = threadPool; } @@ -526,7 +522,6 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL public void start() { try { if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { - this.client = clientProvider.get(); this.scrollSize = SCROLL_SIZE_SETTING.get(settings); this.scrollKeepAlive = SCROLL_KEEP_ALIVE_SETTING.get(settings); @@ -536,8 +531,8 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL } catch (Exception e) { logger.warn("failed to do initial poll of users", e); } - userPoller = new SelfReschedulingRunnable(poller, threadPool, POLL_INTERVAL_SETTING.get(settings), Names.GENERIC, logger); - userPoller.start(); + TimeValue interval = settings.getAsTime("shield.authc.native.reload.interval", TimeValue.timeValueSeconds(30L)); + pollerCancellable = threadPool.scheduleWithFixedDelay(poller, interval, Names.GENERIC); state.set(State.STARTED); } } catch (Exception e) { @@ -549,7 +544,7 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL public void stop() { if (state.compareAndSet(State.STARTED, State.STOPPING)) { try { - userPoller.stop(); + pollerCancellable.cancel(); } catch (Exception e) { state.set(State.FAILED); throw e; @@ -703,7 +698,6 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL throw new IllegalStateException("can only reset if stopped!!!"); } this.listeners.clear(); - this.client = null; this.securityIndexExists = false; this.state.set(State.INITIALIZED); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java index 52aa20550af..4dbafd1b39d 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java @@ -35,7 +35,6 @@ public class ReservedRealm extends CachingUsernamePasswordRealm { private final NativeUsersStore nativeUsersStore; - @Inject public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore) { super(TYPE, new RealmConfig(TYPE, Settings.EMPTY, settings, env)); this.nativeUsersStore = nativeUsersStore; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java index b30bde9b75a..bd17724107c 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileRealm.java @@ -5,19 +5,14 @@ */ package org.elasticsearch.xpack.security.authc.file; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.env.Environment; -import org.elasticsearch.rest.RestController; +import java.util.Map; + +import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.RefreshListener; -import org.elasticsearch.xpack.security.authc.support.UsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.watcher.ResourceWatcherService; - -import java.util.Map; /** * @@ -29,7 +24,12 @@ public class FileRealm extends CachingUsernamePasswordRealm { final FileUserPasswdStore userPasswdStore; final FileUserRolesStore userRolesStore; - public FileRealm(RealmConfig config, FileUserPasswdStore userPasswdStore, FileUserRolesStore userRolesStore) { + public FileRealm(RealmConfig config, ResourceWatcherService watcherService) { + this(config, new FileUserPasswdStore(config, watcherService), new FileUserRolesStore(config, watcherService)); + } + + // pkg private for testing + FileRealm(RealmConfig config, FileUserPasswdStore userPasswdStore, FileUserRolesStore userRolesStore) { super(TYPE, config); Listener listener = new Listener(); this.userPasswdStore = userPasswdStore; @@ -76,31 +76,4 @@ public class FileRealm extends CachingUsernamePasswordRealm { expireAll(); } } - - public static class Factory extends UsernamePasswordRealm.Factory { - - private final Settings settings; - private final Environment env; - private final ResourceWatcherService watcherService; - - @Inject - public Factory(Settings settings, Environment env, ResourceWatcherService watcherService, RestController restController) { - super(TYPE, restController, true); - this.settings = settings; - this.env = env; - this.watcherService = watcherService; - } - - @Override - public FileRealm create(RealmConfig config) { - return new FileRealm(config, new FileUserPasswdStore(config, watcherService), - new FileUserRolesStore(config, watcherService)); - } - - @Override - public FileRealm createDefault(String name) { - RealmConfig config = new RealmConfig(name, Settings.EMPTY, settings, env); - return create(config); - } - } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index 792e6975012..e2de7894095 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -5,19 +5,18 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import java.util.Map; + +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.rest.RestController; +import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.AbstractLdapRealm; import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.ssl.ClientSSLService; -import org.elasticsearch.watcher.ResourceWatcherService; -import java.io.IOException; -import java.util.Map; /** * Authenticates username/password tokens against ldap, locates groups and maps them to roles. @@ -26,55 +25,40 @@ public class LdapRealm extends AbstractLdapRealm { public static final String TYPE = "ldap"; - public LdapRealm(RealmConfig config, SessionFactory ldap, DnRoleMapper roleMapper) { - super(TYPE, config, ldap, roleMapper); + public LdapRealm(RealmConfig config, ResourceWatcherService watcherService, ClientSSLService clientSSLService) { + this(config, sessionFactory(config, clientSSLService), new DnRoleMapper(TYPE, config, watcherService, null)); + } + + // pkg private for testing + LdapRealm(RealmConfig config, SessionFactory sessionFactory, DnRoleMapper roleMapper) { + super(TYPE, config, sessionFactory, roleMapper); + } + + static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) { + Settings searchSettings = userSearchSettings(config); + try { + if (!searchSettings.names().isEmpty()) { + if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { + throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " + + "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap " + + "authentication section of the X-Pack guide."); + } + return new LdapUserSearchSessionFactory(config, clientSSLService); + } + return new LdapSessionFactory(config, clientSSLService); + } catch (LDAPException e) { + throw new ElasticsearchException("failed to create realm [{}/{}]", e, LdapRealm.TYPE, config.name()); + } + } + + static Settings userSearchSettings(RealmConfig config) { + return config.settings().getAsSettings("user_search"); } @Override public Map usageStats() { Map stats = super.usageStats(); - stats.put("user_search", Factory.userSearchSettings(config).isEmpty() == false); + stats.put("user_search", userSearchSettings(config).isEmpty() == false); return stats; } - - public static class Factory extends AbstractLdapRealm.Factory { - - private final ResourceWatcherService watcherService; - private final ClientSSLService clientSSLService; - - @Inject - public Factory(ResourceWatcherService watcherService, ClientSSLService clientSSLService, RestController restController) { - super(TYPE, restController); - this.watcherService = watcherService; - this.clientSSLService = clientSSLService; - } - - @Override - public LdapRealm create(RealmConfig config) { - try { - SessionFactory sessionFactory = sessionFactory(config, clientSSLService); - DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); - return new LdapRealm(config, sessionFactory, roleMapper); - } catch (IOException e) { - throw new ElasticsearchException("failed to create realm [{}/{}]", e, LdapRealm.TYPE, config.name()); - } - } - - static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws IOException { - Settings searchSettings = userSearchSettings(config); - if (!searchSettings.names().isEmpty()) { - if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { - throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " + - "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap " + - "authentication section of the X-Pack guide."); - } - return new LdapUserSearchSessionFactory(config, clientSSLService).init(); - } - return new LdapSessionFactory(config, clientSSLService).init(); - } - - static Settings userSearchSettings(RealmConfig config) { - return config.settings().getAsSettings("user_search"); - } - } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java index f54c4399609..5ffd2a52304 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java @@ -14,9 +14,7 @@ import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsRes import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; -import org.elasticsearch.xpack.security.support.Exceptions; -import java.io.IOException; import java.text.MessageFormat; import java.util.Locale; @@ -54,13 +52,7 @@ public class LdapSessionFactory extends SessionFactory { */ @Override protected LdapSession getSession(String username, SecuredString password) throws Exception { - LDAPConnection connection; - - try { - connection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("failed to connect to any LDAP servers", e); - } + LDAPConnection connection = serverSet.getConnection(); LDAPException lastException = null; String passwordString = new String(password.internalChars()); @@ -68,20 +60,22 @@ public class LdapSessionFactory extends SessionFactory { String dn = buildDnFromTemplate(username, template); try { connection.bind(dn, passwordString); - return new LdapSession(connectionLogger, connection, dn, groupResolver, timeout); + return new LdapSession(logger, connection, dn, groupResolver, timeout, null); } catch (LDAPException e) { - if (logger.isDebugEnabled()) { - logger.debug("failed LDAP authentication with user template [{}] and DN [{}]", e, template, dn); + // we catch the ldapException here since we expect it can happen and we shouldn't be logging this all the time otherwise + // it is just noise + logger.debug("failed LDAP authentication with user template [{}] and DN [{}]", e, template, dn); + if (lastException == null) { + lastException = e; } else { - logger.warn("failed LDAP authentication with user template [{}] and DN [{}]: {}", template, dn, e.getMessage()); + lastException.addSuppressed(e); } - - lastException = e; } } connection.close(); - throw Exceptions.authenticationError("failed LDAP authentication", lastException); + assert lastException != null; + throw lastException; } /** diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java index fd84514a547..77ceefcc7e3 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java @@ -8,12 +8,13 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPConnectionPool; +import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.ServerSet; import com.unboundid.ldap.sdk.SimpleBindRequest; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; @@ -26,28 +27,29 @@ import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.xpack.security.support.Exceptions; -import java.io.IOException; import java.util.Locale; import static com.unboundid.ldap.sdk.Filter.createEqualityFilter; import static com.unboundid.ldap.sdk.Filter.encodeValue; +import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; -public class LdapUserSearchSessionFactory extends SessionFactory { +class LdapUserSearchSessionFactory extends SessionFactory { static final int DEFAULT_CONNECTION_POOL_SIZE = 20; - static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 5; + static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; static final String DEFAULT_USERNAME_ATTRIBUTE = "uid"; static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L); private final String userSearchBaseDn; private final LdapSearchScope scope; private final String userAttribute; + private final GroupsResolver groupResolver; + private final boolean useConnectionPool; - private LDAPConnectionPool connectionPool; - private GroupsResolver groupResolver; + private final LDAPConnectionPool connectionPool; - public LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) { + LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) throws LDAPException { super(config, sslService); Settings settings = config.settings(); userSearchBaseDn = settings.get("user_search.base_dn"); @@ -56,63 +58,52 @@ public class LdapUserSearchSessionFactory extends SessionFactory { } scope = LdapSearchScope.resolve(settings.get("user_search.scope"), LdapSearchScope.SUB_TREE); userAttribute = settings.get("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE); - } - - @Override - public LdapUserSearchSessionFactory init() { - super.init(); - connectionPool = createConnectionPool(config, serverSet, timeout, logger); groupResolver = groupResolver(config.settings()); - return this; - } - - private synchronized LDAPConnectionPool connectionPool() throws IOException { - if (connectionPool == null) { + useConnectionPool = settings.getAsBoolean("user_search.pool.enabled", true); + if (useConnectionPool) { connectionPool = createConnectionPool(config, serverSet, timeout, logger); - // if it is still null throw an exception - if (connectionPool == null) { - String msg = "failed to create a connection pool for realm [" + config.name() + "] as no LDAP servers are available"; - throw new IOException(msg); - } + } else { + connectionPool = null; } - - return connectionPool; } - static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, ESLogger logger) { + static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, ESLogger logger) + throws LDAPException { Settings settings = config.settings(); SimpleBindRequest bindRequest = bindRequest(settings); - int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); - int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + final int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); + final int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + LDAPConnectionPool pool = null; + boolean success = false; try { - LDAPConnectionPool pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); + pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); pool.setRetryFailedOperationsDueToInvalidConnections(true); if (settings.getAsBoolean("user_search.pool.health_check.enabled", true)) { String entryDn = settings.get("user_search.pool.health_check.dn", (bindRequest == null) ? null : bindRequest.getBindDN()); - if (entryDn == null) { - pool.close(); - throw new IllegalArgumentException("[bind_dn] has not been specified so a value must be specified for [user_search" + - ".pool.health_check.dn] or [user_search.pool.health_check.enabled] must be set to false"); + final long healthCheckInterval = + settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL).millis(); + if (entryDn != null) { + // Checks the status of the LDAP connection at a specified interval in the background. We do not check on + // on create as the LDAP server may require authentication to get an entry and a bind request has not been executed + // yet so we could end up never getting a connection. We do not check on checkout as we always set retry operations + // and the pool will handle a bad connection without the added latency on every operation + LDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(), + false, false, false, true, false); + pool.setHealthCheck(healthCheck); + pool.setHealthCheckIntervalMillis(healthCheckInterval); + } else { + logger.warn("[bind_dn] and [user_search.pool.health_check.dn] have not been specified so no " + + "ldap query will be run as a health check"); } - long healthCheckInterval = settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL) - .millis(); - // Checks the status of the LDAP connection at a specified interval in the background. We do not check on - // on create as the LDAP server may require authentication to get an entry. We do not check on checkout - // as we always set retry operations and the pool will handle a bad connection without the added latency on every operation - GetEntryLDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(), - false, false, false, true, false); - pool.setHealthCheck(healthCheck); - pool.setHealthCheckIntervalMillis(healthCheckInterval); } + + success = true; return pool; - } catch (LDAPException e) { - if (logger.isDebugEnabled()) { - logger.debug("unable to create connection pool for realm [{}]", e, config.name()); - } else { - logger.error("unable to create connection pool for realm [{}]: {}", config.name(), e.getMessage()); + } finally { + if (success == false && pool != null) { + pool.close(); } } - return null; } static SimpleBindRequest bindRequest(Settings settings) { @@ -126,12 +117,38 @@ public class LdapUserSearchSessionFactory extends SessionFactory { @Override protected LdapSession getSession(String user, SecuredString password) throws Exception { + if (useConnectionPool) { + return getSessionWithPool(user, password); + } else { + return getSessionWithoutPool(user, password); + } + } + + private LdapSession getSessionWithPool(String user, SecuredString password) throws Exception { + SearchResultEntry searchResult = findUser(user, connectionPool); + assert searchResult != null; + final String dn = searchResult.getDN(); + connectionPool.bindAndRevertAuthentication(dn, new String(password.internalChars())); + return new LdapSession(logger, connectionPool, dn, groupResolver, timeout, searchResult.getAttributes()); + } + + private LdapSession getSessionWithoutPool(String user, SecuredString password) throws Exception { + boolean success = false; + LDAPConnection connection = null; try { - String dn = findUserDN(user); - tryBind(dn, password); - return new LdapSession(logger, connectionPool, dn, groupResolver, timeout); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to authenticate user [{}]", e, user); + connection = serverSet.getConnection(); + connection.bind(bindRequest(config.settings())); + SearchResultEntry searchResult = findUser(user, connection); + assert searchResult != null; + final String dn = searchResult.getDN(); + connection.bind(dn, new String(password.internalChars())); + LdapSession session = new LdapSession(logger, connection, dn, groupResolver, timeout, searchResult.getAttributes()); + success = true; + return session; + } finally { + if (success == false && connection != null) { + connection.close(); + } } } @@ -142,46 +159,45 @@ public class LdapUserSearchSessionFactory extends SessionFactory { @Override public LdapSession unauthenticatedSession(String user) throws Exception { + LDAPConnection connection = null; + boolean success = false; try { - String dn = findUserDN(user); - return new LdapSession(logger, connectionPool, dn, groupResolver, timeout); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to lookup user [{}]", e, user); + final LDAPInterface ldapInterface; + if (useConnectionPool) { + ldapInterface = connectionPool; + } else { + connection = serverSet.getConnection(); + connection.bind(bindRequest(config.settings())); + ldapInterface = connection; + } + + SearchResultEntry searchResult = findUser(user, ldapInterface); + assert searchResult != null; + final String dn = searchResult.getDN(); + LdapSession session = new LdapSession(logger, ldapInterface, dn, groupResolver, timeout, searchResult.getAttributes()); + success = true; + return session; + } finally { + if (success == false && connection != null) { + connection.close(); + } } } - private String findUserDN(String user) throws Exception { - SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)) - , SearchRequest.NO_ATTRIBUTES); + private SearchResultEntry findUser(String user, LDAPInterface ldapInterface) throws Exception { + SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)), + attributesToSearchFor(groupResolver.attributes())); request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - LDAPConnectionPool connectionPool = connectionPool(); - SearchResultEntry entry = searchForEntry(connectionPool, request, logger); + SearchResultEntry entry = searchForEntry(ldapInterface, request, logger); if (entry == null) { throw Exceptions.authenticationError("failed to find user [{}] with search base [{}] scope [{}]", user, userSearchBaseDn, scope.toString().toLowerCase(Locale.ENGLISH)); } - return entry.getDN(); - } - - private void tryBind(String dn, SecuredString password) throws IOException { - LDAPConnection bindConnection; - try { - bindConnection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("unable to connect to any LDAP servers for bind", e); - } - - try { - bindConnection.bind(dn, new String(password.internalChars())); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed LDAP authentication for DN [{}]", e, dn); - } finally { - bindConnection.close(); - } + return entry; } /* - * This method is used to cleanup the connections for tests + * This method is used to cleanup the connections */ void shutdown() { if (connectionPool != null) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java index 12600548baf..52d668ec086 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java @@ -12,15 +12,15 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; -import org.elasticsearch.xpack.security.support.Exceptions; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; @@ -29,20 +29,21 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.sear import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; /** -* -*/ + * Resolves the groups for a user by executing a search with a filter usually that contains a group object class with a attribute that + * matches an ID of the user + */ class SearchGroupsResolver implements GroupsResolver { private static final String GROUP_SEARCH_DEFAULT_FILTER = "(&" + - "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group))" + - "(|(uniqueMember={0})(member={0})))"; + "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group)(objectclass=posixGroup))" + + "(|(uniqueMember={0})(member={0})(memberUid={0})))"; private final String baseDn; private final String filter; private final String userAttribute; private final LdapSearchScope scope; - public SearchGroupsResolver(Settings settings) { + SearchGroupsResolver(Settings settings) { baseDn = settings.get("base_dn"); if (baseDn == null) { throw new IllegalArgumentException("base_dn must be specified"); @@ -53,37 +54,60 @@ class SearchGroupsResolver implements GroupsResolver { } @Override - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - List groups = new LinkedList<>(); - - String userId = userAttribute != null ? readUserAttribute(connection, userDn, timeout, logger) : userDn; - try { - SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), createFilter(filter, userId), - SearchRequest.NO_ATTRIBUTES); - searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResult results = search(connection, searchRequest, logger); - for (SearchResultEntry entry : results.getSearchEntries()) { - groups.add(entry.getDN()); - } - } catch (LDAPException e) { - throw Exceptions.authenticationError("could not search for LDAP groups for DN [{}]", e, userDn); + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException { + String userId = getUserId(userDn, attributes, connection, timeout, logger); + if (userId == null) { + // attributes were queried but the requested wasn't found + return Collections.emptyList(); } + SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), createFilter(filter, userId), + SearchRequest.NO_ATTRIBUTES); + searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResult results = search(connection, searchRequest, logger); + List groups = new ArrayList<>(results.getSearchEntries().size()); + for (SearchResultEntry entry : results.getSearchEntries()) { + groups.add(entry.getDN()); + } return groups; } - String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - try { - SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); - request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResultEntry results = searchForEntry(connection, request, logger); - Attribute attribute = results.getAttribute(userAttribute); - if (attribute == null) { - throw Exceptions.authenticationError("no results returned for DN [{}] attribute [{}]", userDn, userAttribute); - } - return attribute.getValue(); - } catch (LDAPException e) { - throw Exceptions.authenticationError("could not retrieve attribute [{}] for DN [{}]", e, userAttribute, userDn); + public String[] attributes() { + if (userAttribute != null) { + return new String[] { userAttribute }; } + return null; + } + + private String getUserId(String dn, Collection attributes, LDAPInterface connection, TimeValue + timeout, ESLogger logger) throws LDAPException { + if (userAttribute == null) { + return dn; + } + + if (attributes != null) { + for (Attribute attribute : attributes) { + if (attribute.getName().equals(userAttribute)) { + return attribute.getValue(); + } + } + } + + return readUserAttribute(connection, dn, timeout, logger); + } + + String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) throws LDAPException { + SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); + request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResultEntry results = searchForEntry(connection, request, logger); + if (results == null) { + return null; + } + Attribute attribute = results.getAttribute(userAttribute); + if (attribute == null) { + return null; + } + return attribute.getValue(); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java index 9ae30969696..e04d9924063 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java @@ -11,48 +11,64 @@ import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; /** -* +* Resolves the groups of a user based on the value of a attribute of the user's ldap entry */ class UserAttributeGroupsResolver implements GroupsResolver { private final String attribute; - public UserAttributeGroupsResolver(Settings settings) { + UserAttributeGroupsResolver(Settings settings) { this(settings.get("user_group_attribute", "memberOf")); } - public UserAttributeGroupsResolver(String attribute) { - this.attribute = attribute; + private UserAttributeGroupsResolver(String attribute) { + this.attribute = Objects.requireNonNull(attribute); } @Override - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - try { - SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); - request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResultEntry result = searchForEntry(connection, request, logger); - Attribute attributeReturned = result.getAttribute(attribute); - if (attributeReturned == null) { - return Collections.emptyList(); + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException { + if (attributes != null) { + for (Attribute attribute : attributes) { + if (attribute.getName().equals(attribute)) { + String[] values = attribute.getValues(); + return Arrays.asList(values); + } } - String[] values = attributeReturned.getValues(); - return Arrays.asList(values); - } catch (LDAPException e) { - throw new ElasticsearchException("could not look up group attributes for DN [{}]", e, userDn); + return Collections.emptyList(); } + + SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); + request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResultEntry result = searchForEntry(connection, request, logger); + if (result == null) { + return Collections.emptyList(); + } + Attribute attributeReturned = result.getAttribute(attribute); + if (attributeReturned == null) { + return Collections.emptyList(); + } + String[] values = attributeReturned.getValues(); + return Arrays.asList(values); + } + + @Override + public String[] attributes() { + return new String[] { attribute }; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java index 1f6c9611282..8165c8dce03 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java @@ -5,19 +5,18 @@ */ package org.elasticsearch.xpack.security.authc.ldap.support; -import org.elasticsearch.rest.RestController; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.authc.support.RefreshListener; -import org.elasticsearch.xpack.security.authc.support.UsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import java.util.List; -import java.util.Map; -import java.util.Set; - /** * Supporting class for LDAP realms */ @@ -88,7 +87,7 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm { } } - private User createUser(String principal, LdapSession session) { + private User createUser(String principal, LdapSession session) throws LDAPException { List groupDNs = session.groups(); Set roles = roleMapper.resolveRoles(session.userDn(), groupDNs); return new User(principal, roles.toArray(new String[roles.size()])); @@ -100,21 +99,4 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm { expireAll(); } } - - public abstract static class Factory extends UsernamePasswordRealm.Factory { - - public Factory(String type, RestController restController) { - super(type, restController, false); - } - - /** - * LDAP realms require minimum settings (e.g. URL), therefore they'll never create a default. - * - * @return {@code null} always - */ - @Override - public final R createDefault(String name) { - return null; - } - } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java index f6896d8498a..a41e28e7e86 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java @@ -5,12 +5,15 @@ */ package org.elasticsearch.xpack.security.authc.ldap.support; +import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPInterface; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.unit.TimeValue; import java.io.Closeable; +import java.util.Collection; import java.util.List; /** @@ -23,6 +26,7 @@ public class LdapSession implements Closeable { protected final String userDn; protected final GroupsResolver groupsResolver; protected final TimeValue timeout; + protected final Collection attributes; /** * This object is intended to be constructed by the LdapConnectionFactory @@ -32,12 +36,14 @@ public class LdapSession implements Closeable { * outside of and be reused across all connections. We can't keep a static logger in this class * since we want the logger to be contextual (i.e. aware of the settings and its environment). */ - public LdapSession(ESLogger logger, LDAPInterface connection, String userDn, GroupsResolver groupsResolver, TimeValue timeout) { + public LdapSession(ESLogger logger, LDAPInterface connection, String userDn, GroupsResolver groupsResolver, TimeValue timeout, + Collection attributes) { this.logger = logger; this.ldap = connection; this.userDn = userDn; this.groupsResolver = groupsResolver; this.timeout = timeout; + this.attributes = attributes; } /** @@ -61,13 +67,15 @@ public class LdapSession implements Closeable { /** * @return List of fully distinguished group names */ - public List groups() { - return groupsResolver.resolve(ldap, userDn, timeout, logger); + public List groups() throws LDAPException { + return groupsResolver.resolve(ldap, userDn, timeout, logger, attributes); } public interface GroupsResolver { - List resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger); + List resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException; + String[] attributes(); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java index a66964e4e76..f2ccb1b4fbb 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java @@ -46,7 +46,7 @@ public final class LdapUtils { /** * This method performs a LDAPConnection.search(...) operation while handling referral exceptions. This is necessary - * to maintain backwards compatibility + * to maintain backwards compatibility with the original JNDI implementation */ public static SearchResult search(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException { SearchResult results; @@ -68,7 +68,7 @@ public final class LdapUtils { /** * This method performs a LDAPConnection.searchForEntry(...) operation while handling referral exceptions. This is necessary - * to maintain backwards compatibility + * to maintain backwards compatibility with the original JNDI implementation */ public static SearchResultEntry searchForEntry(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException { SearchResultEntry entry; @@ -93,6 +93,10 @@ public final class LdapUtils { new StringBuffer(), null).toString()); } + public static String[] attributesToSearchFor(String[] attributes) { + return attributes == null ? new String[] { SearchRequest.NO_ATTRIBUTES } : attributes; + } + static String[] encodeFilterValues(String... arguments) { for (int i = 0; i < arguments.length; i++) { arguments[i] = Filter.encodeValue(arguments[i]); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java index e707247cbc1..cd8799486b7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java @@ -48,18 +48,16 @@ public abstract class SessionFactory { private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE); protected final ESLogger logger; - protected final ESLogger connectionLogger; protected final RealmConfig config; protected final TimeValue timeout; protected final ClientSSLService sslService; - protected ServerSet serverSet; - protected boolean sslUsed; + protected final ServerSet serverSet; + protected final boolean sslUsed; protected SessionFactory(RealmConfig config, ClientSSLService sslService) { this.config = config; this.logger = config.logger(getClass()); - this.connectionLogger = config.logger(getClass()); TimeValue searchTimeout = config.settings().getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT); if (searchTimeout.millis() < 1000L) { logger.warn("ldap_search timeout [{}] is less than the minimum supported search timeout of 1s. using 1s", @@ -68,6 +66,9 @@ public abstract class SessionFactory { } this.timeout = searchTimeout; this.sslService = sslService; + LDAPServers ldapServers = ldapServers(config.settings()); + this.serverSet = serverSet(config.settings(), sslService, ldapServers); + this.sslUsed = ldapServers.ssl; } /** @@ -80,9 +81,6 @@ public abstract class SessionFactory { * @throws Exception if an error occurred when creating the session */ public final LdapSession session(String user, SecuredString password) throws Exception { - if (serverSet == null) { - throw new IllegalStateException("session factory is not initialized"); - } return getSession(user, password); } @@ -119,19 +117,11 @@ public abstract class SessionFactory { throw new UnsupportedOperationException("unauthenticated sessions are not supported"); } - public T init() { - LDAPServers ldapServers = ldapServers(config.settings()); - this.serverSet = serverSet(config.settings(), sslService, ldapServers); - this.sslUsed = ldapServers.ssl; - return (T) this; - } - protected static LDAPConnectionOptions connectionOptions(Settings settings) { LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setConnectTimeoutMillis(Math.toIntExact(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis())); options.setFollowReferrals(settings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true)); options.setResponseTimeoutMillis(settings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis()); - options.setAutoReconnect(true); options.setAllowConcurrentSocketFactoryUse(true); if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) { options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true)); @@ -139,15 +129,19 @@ public abstract class SessionFactory { return options; } - protected LDAPServers ldapServers(Settings settings) { + protected final LDAPServers ldapServers(Settings settings) { // Parse LDAP urls - String[] ldapUrls = settings.getAsArray(URLS_SETTING); + String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings)); if (ldapUrls == null || ldapUrls.length == 0) { throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]"); } return new LDAPServers(ldapUrls); } + protected String[] getDefaultLdapUrls(Settings settings) { + return null; + } + protected ServerSet serverSet(Settings settings, ClientSSLService clientSSLService, LDAPServers ldapServers) { SocketFactory socketFactory = null; if (ldapServers.ssl()) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java index fff17be10e0..d128d25f092 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java @@ -18,8 +18,8 @@ import org.elasticsearch.xpack.security.authc.Realm; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.transport.SSLClientAuth; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.watcher.ResourceWatcherService; import javax.net.ssl.TrustManager; @@ -38,7 +38,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class PkiRealm extends Realm { +public class PkiRealm extends Realm { public static final String PKI_CERT_HEADER_NAME = "__SECURITY_CLIENT_CERTIFICATE"; public static final String TYPE = "pki"; @@ -51,7 +51,13 @@ public class PkiRealm extends Realm { private final Pattern principalPattern; private final DnRoleMapper roleMapper; - public PkiRealm(RealmConfig config, DnRoleMapper roleMapper) { + + public PkiRealm(RealmConfig config, ResourceWatcherService watcherService) { + this(config, new DnRoleMapper(TYPE, config, watcherService, null)); + } + + // pkg private for testing + PkiRealm(RealmConfig config, DnRoleMapper roleMapper) { super(TYPE, config); this.trustManagers = trustManagers(config.settings(), config.env()); this.principalPattern = Pattern.compile(config.settings().get("username_pattern", DEFAULT_USERNAME_PATTERN), @@ -71,7 +77,8 @@ public class PkiRealm extends Realm { } @Override - public User authenticate(X509AuthenticationToken token) { + public User authenticate(AuthenticationToken authToken) { + X509AuthenticationToken token = (X509AuthenticationToken)authToken; if (!isCertificateChainTrusted(trustManagers, token, logger)) { return null; } @@ -195,16 +202,16 @@ public class PkiRealm extends Realm { static void checkSSLEnabled(RealmConfig config, ESLogger logger) { Settings settings = config.globalSettings(); - final boolean httpSsl = SecurityNettyHttpServerTransport.SSL_SETTING.get(settings); - final boolean httpClientAuth = SecurityNettyHttpServerTransport.CLIENT_AUTH_SETTING.get(settings).enabled(); + final boolean httpSsl = SecurityNetty3HttpServerTransport.SSL_SETTING.get(settings); + final boolean httpClientAuth = SecurityNetty3HttpServerTransport.CLIENT_AUTH_SETTING.get(settings).enabled(); // HTTP if (httpSsl && httpClientAuth) { return; } // Default Transport - final boolean ssl = SecurityNettyTransport.SSL_SETTING.get(settings); - final SSLClientAuth clientAuth = SecurityNettyTransport.CLIENT_AUTH_SETTING.get(settings); + final boolean ssl = SecurityNetty3Transport.SSL_SETTING.get(settings); + final SSLClientAuth clientAuth = SecurityNetty3Transport.CLIENT_AUTH_SETTING.get(settings); if (ssl && clientAuth.enabled()) { return; } @@ -213,8 +220,8 @@ public class PkiRealm extends Realm { Map groupedSettings = settings.getGroups("transport.profiles."); for (Map.Entry entry : groupedSettings.entrySet()) { Settings profileSettings = entry.getValue().getByPrefix(Security.settingPrefix()); - if (SecurityNettyTransport.profileSsl(profileSettings, settings) - && SecurityNettyTransport.CLIENT_AUTH_SETTING.get(profileSettings, settings).enabled()) { + if (SecurityNetty3Transport.profileSsl(profileSettings, settings) + && SecurityNetty3Transport.CLIENT_AUTH_SETTING.get(profileSettings, settings).enabled()) { return; } } @@ -222,26 +229,4 @@ public class PkiRealm extends Realm { logger.error("PKI realm [{}] is enabled but cannot be used as neither HTTP or Transport have both SSL and client authentication " + "enabled", config.name()); } - - public static class Factory extends Realm.Factory { - - private final ResourceWatcherService watcherService; - - @Inject - public Factory(ResourceWatcherService watcherService) { - super(TYPE, false); - this.watcherService = watcherService; - } - - @Override - public PkiRealm create(RealmConfig config) { - DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); - return new PkiRealm(config, roleMapper); - } - - @Override - public PkiRealm createDefault(String name) { - return null; - } - } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java index 5cd1e69288d..54e5b59b1d6 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java @@ -10,6 +10,8 @@ import org.elasticsearch.common.cache.Cache; import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.cache.CacheLoader; import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authc.AuthenticationToken; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.support.Exceptions; import org.elasticsearch.xpack.security.user.User; @@ -63,11 +65,12 @@ public abstract class CachingUsernamePasswordRealm extends UsernamePasswordRealm * against a hash also stored in the cache. Otherwise the subclass authenticates the user via * doAuthenticate * - * @param token The authentication token + * @param authToken The authentication token * @return an authenticated user with roles */ @Override - public final User authenticate(final UsernamePasswordToken token) { + public final User authenticate(AuthenticationToken authToken) { + UsernamePasswordToken token = (UsernamePasswordToken)authToken; if (cache == null) { return doAuthenticate(token); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java index 2eefccdc2db..cb15f785ab4 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java @@ -137,9 +137,7 @@ public class DnRoleMapper { if (dnToRoles.isEmpty()) { logger.warn("no mappings found in role mappings file [{}] for realm [{}/{}]", path.toAbsolutePath(), realmType, realmName); } - return unmodifiableMap(dnToRoles); - } catch (IOException e) { throw new ElasticsearchException("could not read realm [" + realmType + "/" + realmName + "] role mappings file [" + path.toAbsolutePath() + "]", e); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UsernamePasswordRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UsernamePasswordRealm.java index c37f7f8458b..1bac5a68aab 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UsernamePasswordRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UsernamePasswordRealm.java @@ -16,7 +16,7 @@ import java.util.Locale; /** * */ -public abstract class UsernamePasswordRealm extends Realm { +public abstract class UsernamePasswordRealm extends Realm { public UsernamePasswordRealm(String type, RealmConfig config) { super(type, config); @@ -31,14 +31,6 @@ public abstract class UsernamePasswordRealm extends Realm return token instanceof UsernamePasswordToken; } - public abstract static class Factory extends Realm.Factory { - - protected Factory(String type, RestController restController, boolean internal) { - super(type, internal); - restController.registerRelevantHeaders(UsernamePasswordToken.BASIC_AUTH_HEADER); - } - } - public enum UserbaseSize { TINY, diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationModule.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationModule.java deleted file mode 100644 index 81b584f0d81..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationModule.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.authz; - -import org.elasticsearch.common.inject.util.Providers; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; -import org.elasticsearch.xpack.security.authz.store.FileRolesStore; -import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; -import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; -import org.elasticsearch.xpack.security.authz.store.RolesStore; -import org.elasticsearch.xpack.security.support.AbstractSecurityModule; - -/** - * Module used to bind various classes necessary for authorization - */ -public class AuthorizationModule extends AbstractSecurityModule.Node { - - public AuthorizationModule(Settings settings) { - super(settings); - } - - @Override - protected void configureNode() { - if (securityEnabled == false) { - bind(RolesStore.class).toProvider(Providers.of(null)); - return; - } - - // First the file and native roles stores must be bound... - bind(ReservedRolesStore.class).asEagerSingleton(); - bind(FileRolesStore.class).asEagerSingleton(); - bind(NativeRolesStore.class).asEagerSingleton(); - // Then the composite roles store (which combines both) can be bound - bind(RolesStore.class).to(CompositeRolesStore.class).asEagerSingleton(); - bind(AuthorizationService.class).to(InternalAuthorizationService.class).asEagerSingleton(); - } - -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index d80f693f2d5..1a9560d50d6 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -5,17 +5,94 @@ */ package org.elasticsearch.xpack.security.authz; -import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.transport.TransportRequest; - +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.CompositeIndicesRequest; +import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.action.admin.indices.alias.Alias; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; +import org.elasticsearch.action.search.ClearScrollAction; +import org.elasticsearch.action.search.SearchScrollAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.metadata.AliasOrIndex; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.search.action.SearchTransportService; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.security.SecurityTemplateService; +import org.elasticsearch.xpack.security.audit.AuditTrail; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler; +import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; +import org.elasticsearch.xpack.security.authz.indicesresolver.DefaultIndicesAndAliasesResolver; +import org.elasticsearch.xpack.security.authz.indicesresolver.IndicesAndAliasesResolver; +import org.elasticsearch.xpack.security.authz.permission.ClusterPermission; +import org.elasticsearch.xpack.security.authz.permission.DefaultRole; +import org.elasticsearch.xpack.security.authz.permission.GlobalPermission; +import org.elasticsearch.xpack.security.authz.permission.Role; +import org.elasticsearch.xpack.security.authz.permission.RunAsPermission; +import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.user.AnonymousUser; +import org.elasticsearch.xpack.security.user.SystemUser; +import org.elasticsearch.xpack.security.user.User; +import org.elasticsearch.xpack.security.user.XPackUser; +import org.elasticsearch.xpack.security.authz.store.RolesStore; + +import static org.elasticsearch.xpack.security.Security.setting; +import static org.elasticsearch.xpack.security.support.Exceptions.authorizationError; /** * */ -public interface AuthorizationService { +public class AuthorizationService extends AbstractComponent { + + public static final Setting ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING = + Setting.boolSetting(setting("authc.anonymous.authz_exception"), true, Property.NodeScope); + public static final String INDICES_PERMISSIONS_KEY = "_indices_permissions"; + static final String ORIGINATING_ACTION_KEY = "_originating_action_name"; + + private static final Predicate MONITOR_INDEX_PREDICATE = IndexPrivilege.MONITOR.predicate(); + + private final ClusterService clusterService; + private final CompositeRolesStore rolesStore; + private final AuditTrailService auditTrail; + private final IndicesAndAliasesResolver[] indicesAndAliasesResolvers; + private final AuthenticationFailureHandler authcFailureHandler; + private final ThreadContext threadContext; + private final boolean anonymousAuthzExceptionEnabled; + + public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, ClusterService clusterService, + AuditTrailService auditTrail, AuthenticationFailureHandler authcFailureHandler, + ThreadPool threadPool) { + super(settings); + this.rolesStore = rolesStore; + this.clusterService = clusterService; + this.auditTrail = auditTrail; + this.indicesAndAliasesResolvers = new IndicesAndAliasesResolver[] { + new DefaultIndicesAndAliasesResolver(this, new IndexNameExpressionResolver(settings)) + }; + this.authcFailureHandler = authcFailureHandler; + this.threadContext = threadPool.getThreadContext(); + this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings); + } /** * Returns all indices and aliases the given user is allowed to execute the given action on. @@ -23,7 +100,49 @@ public interface AuthorizationService { * @param user The user * @param action The action */ - List authorizedIndicesAndAliases(User user, String action); + public List authorizedIndicesAndAliases(User user, String action) { + final String[] anonymousRoles = AnonymousUser.enabled() ? AnonymousUser.getRoles() : Strings.EMPTY_ARRAY; + String[] rolesNames = user.roles(); + if (rolesNames.length == 0 && anonymousRoles.length == 0) { + return Collections.emptyList(); + } + + List> predicates = new ArrayList<>(); + for (String roleName : rolesNames) { + Role role = rolesStore.role(roleName); + if (role != null) { + predicates.add(role.indices().allowedIndicesMatcher(action)); + } + } + if (AnonymousUser.is(user) == false) { + for (String roleName : anonymousRoles) { + Role role = rolesStore.role(roleName); + if (role != null) { + predicates.add(role.indices().allowedIndicesMatcher(action)); + } + } + } + Predicate predicate = predicates.stream().reduce(s -> false, (p1, p2) -> p1.or(p2)); + + List indicesAndAliases = new ArrayList<>(); + MetaData metaData = clusterService.state().metaData(); + // TODO: can this be done smarter? I think there are usually more indices/aliases in the cluster then indices defined a roles? + for (Map.Entry entry : metaData.getAliasAndIndexLookup().entrySet()) { + String aliasOrIndex = entry.getKey(); + if (predicate.test(aliasOrIndex)) { + indicesAndAliases.add(aliasOrIndex); + } + } + + if (XPackUser.is(user) == false) { + // we should filter out the .security index from wildcards + if (indicesAndAliases.remove(SecurityTemplateService.SECURITY_INDEX_NAME)) { + logger.debug("removed [{}] from user [{}] list of authorized indices", + SecurityTemplateService.SECURITY_INDEX_NAME, user.principal()); + } + } + return Collections.unmodifiableList(indicesAndAliases); + } /** * Verifies that the given user can execute the given request (and action). If the user doesn't @@ -35,6 +154,226 @@ public interface AuthorizationService { * @param request The request * @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request */ - void authorize(Authentication authentication, String action, TransportRequest request) throws ElasticsearchSecurityException; + public void authorize(Authentication authentication, String action, TransportRequest request) throws ElasticsearchSecurityException { + // prior to doing any authorization lets set the originating action in the context only + setOriginatingAction(action); + // first we need to check if the user is the system. If it is, we'll just authorize the system access + if (SystemUser.is(authentication.getRunAsUser())) { + if (SystemUser.isAuthorized(action) && SystemUser.is(authentication.getUser())) { + setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); + grant(authentication, action, request); + return; + } + throw denial(authentication, action, request); + } + + // get the roles of the authenticated user, which may be different than the effective + GlobalPermission permission = permission(authentication.getUser().roles()); + + final boolean isRunAs = authentication.getUser() != authentication.getRunAsUser(); + // permission can be null as it might be that the user's role + // is unknown + if (permission == null || permission.isEmpty()) { + if (isRunAs) { + // the request is a run as request so we should call the specific audit event for a denied run as attempt + throw denyRunAs(authentication, action, request); + } else { + throw denial(authentication, action, request); + } + } + + // check if the request is a run as request + if (isRunAs) { + // first we must authorize for the RUN_AS action + RunAsPermission runAs = permission.runAs(); + if (runAs != null && runAs.check(authentication.getRunAsUser().principal())) { + grantRunAs(authentication, action, request); + permission = permission(authentication.getRunAsUser().roles()); + + // permission can be null as it might be that the user's role + // is unknown + if (permission == null || permission.isEmpty()) { + throw denial(authentication, action, request); + } + } else { + throw denyRunAs(authentication, action, request); + } + } + + // first, we'll check if the action is a cluster action. If it is, we'll only check it + // against the cluster permissions + if (ClusterPrivilege.ACTION_MATCHER.test(action)) { + ClusterPermission cluster = permission.cluster(); + // we use the effectiveUser for permission checking since we are running as a user! + if (cluster != null && cluster.check(action, request, authentication)) { + setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); + grant(authentication, action, request); + return; + } + throw denial(authentication, action, request); + } + + // ok... this is not a cluster action, let's verify it's an indices action + if (!IndexPrivilege.ACTION_MATCHER.test(action)) { + throw denial(authentication, action, request); + } + + // some APIs are indices requests that are not actually associated with indices. For example, + // search scroll request, is categorized under the indices context, but doesn't hold indices names + // (in this case, the security check on the indices was done on the search request that initialized + // the scroll... and we rely on the signed scroll id to provide security over this request). + // so we only check indices if indeed the request is an actual IndicesRequest, if it's not, + // we just grant it if it's a scroll, deny otherwise + if (!(request instanceof IndicesRequest) && !(request instanceof CompositeIndicesRequest)) { + if (isScrollRelatedAction(action)) { + //note that clear scroll shard level actions can originate from a clear scroll all, which doesn't require any + //indices permission as it's categorized under cluster. This is why the scroll check is performed + //even before checking if the user has any indices permission. + grant(authentication, action, request); + return; + } + assert false : "only scroll related requests are known indices api that don't support retrieving the indices they relate to"; + throw denial(authentication, action, request); + } + + if (permission.indices() == null || permission.indices().isEmpty()) { + throw denial(authentication, action, request); + } + + ClusterState clusterState = clusterService.state(); + Set indexNames = resolveIndices(authentication, action, request, clusterState); + assert !indexNames.isEmpty() : "every indices request needs to have its indices set thus the resolved indices must not be empty"; + MetaData metaData = clusterState.metaData(); + IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData); + if (!indicesAccessControl.isGranted()) { + throw denial(authentication, action, request); + } else if (indicesAccessControl.getIndexPermissions(SecurityTemplateService.SECURITY_INDEX_NAME) != null + && indicesAccessControl.getIndexPermissions(SecurityTemplateService.SECURITY_INDEX_NAME).isGranted() + && XPackUser.is(authentication.getRunAsUser()) == false + && MONITOR_INDEX_PREDICATE.test(action) == false) { + // only the XPackUser is allowed to work with this index, but we should allow indices monitoring actions through for debugging + // purposes. These monitor requests also sometimes resolve indices concretely and then requests them + // FIXME its not just the XPackUser. We said the elastic user and superusers could access this! + logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]", + authentication.getRunAsUser().principal(), action, SecurityTemplateService.SECURITY_INDEX_NAME); + throw denial(authentication, action, request); + } else { + setIndicesAccessControl(indicesAccessControl); + } + + //if we are creating an index we need to authorize potential aliases created at the same time + if (IndexPrivilege.CREATE_INDEX_MATCHER.test(action)) { + assert request instanceof CreateIndexRequest; + Set aliases = ((CreateIndexRequest) request).aliases(); + if (!aliases.isEmpty()) { + Set aliasesAndIndices = Sets.newHashSet(indexNames); + for (Alias alias : aliases) { + aliasesAndIndices.add(alias.name()); + } + indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData); + if (!indicesAccessControl.isGranted()) { + throw denial(authentication, "indices:admin/aliases", request); + } + // no need to re-add the indicesAccessControl in the context, + // because the create index call doesn't do anything FLS or DLS + } + } + + grant(authentication, action, request); + } + + private void setIndicesAccessControl(IndicesAccessControl accessControl) { + if (threadContext.getTransient(INDICES_PERMISSIONS_KEY) == null) { + threadContext.putTransient(INDICES_PERMISSIONS_KEY, accessControl); + } + } + + private void setOriginatingAction(String action) { + String originatingAction = threadContext.getTransient(ORIGINATING_ACTION_KEY); + if (originatingAction == null) { + threadContext.putTransient(ORIGINATING_ACTION_KEY, action); + } + } + + private GlobalPermission permission(String[] roleNames) { + if (roleNames.length == 0) { + return DefaultRole.INSTANCE; + } + + if (roleNames.length == 1) { + Role role = rolesStore.role(roleNames[0]); + return role == null ? DefaultRole.INSTANCE : GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE).add(role).build(); + } + + // we'll take all the roles and combine their associated permissions + + GlobalPermission.Compound.Builder roles = GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE); + for (String roleName : roleNames) { + Role role = rolesStore.role(roleName); + if (role != null) { + roles.add(role); + } + } + return roles.build(); + } + + private Set resolveIndices(Authentication authentication, String action, TransportRequest request, ClusterState clusterState) { + MetaData metaData = clusterState.metaData(); + for (IndicesAndAliasesResolver resolver : indicesAndAliasesResolvers) { + if (resolver.requestType().isInstance(request)) { + return resolver.resolve(authentication.getRunAsUser(), action, request, metaData); + } + } + assert false : "we should be able to resolve indices for any known request that requires indices privileges"; + throw denial(authentication, action, request); + } + + private static boolean isScrollRelatedAction(String action) { + return action.equals(SearchScrollAction.NAME) || + action.equals(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME) || + action.equals(SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME) || + action.equals(SearchTransportService.QUERY_SCROLL_ACTION_NAME) || + action.equals(SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME) || + action.equals(ClearScrollAction.NAME) || + action.equals(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); + } + + private ElasticsearchSecurityException denial(Authentication authentication, String action, TransportRequest request) { + auditTrail.accessDenied(authentication.getUser(), action, request); + return denialException(authentication, action); + } + + private ElasticsearchSecurityException denyRunAs(Authentication authentication, String action, TransportRequest request) { + auditTrail.runAsDenied(authentication.getUser(), action, request); + return denialException(authentication, action); + } + + private void grant(Authentication authentication, String action, TransportRequest request) { + auditTrail.accessGranted(authentication.getUser(), action, request); + } + + private void grantRunAs(Authentication authentication, String action, TransportRequest request) { + auditTrail.runAsGranted(authentication.getUser(), action, request); + } + + private ElasticsearchSecurityException denialException(Authentication authentication, String action) { + final User user = authentication.getUser(); + // Special case for anonymous user + if (AnonymousUser.enabled() && AnonymousUser.is(user)) { + if (anonymousAuthzExceptionEnabled == false) { + throw authcFailureHandler.authenticationRequired(action, threadContext); + } + } + // check for run as + if (user != authentication.getRunAsUser()) { + return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, user.principal(), + authentication.getRunAsUser().principal()); + } + return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal()); + } + + public static void addSettings(List> settings) { + settings.add(ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING); + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java index 955e64f2258..b3c7344e68e 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java @@ -8,8 +8,6 @@ package org.elasticsearch.xpack.security.authz; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.user.SystemUser; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.authc.InternalAuthenticationService; import org.elasticsearch.xpack.security.support.AutomatonPredicate; import org.elasticsearch.xpack.security.support.Automatons; @@ -52,7 +50,7 @@ public final class AuthorizationUtils { // we have a internal action being executed by a user that is not the system user, lets verify that there is a // originating action that is not a internal action - final String originatingAction = threadContext.getTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY); + final String originatingAction = threadContext.getTransient(AuthorizationService.ORIGINATING_ACTION_KEY); if (originatingAction != null && isInternalAction(originatingAction) == false) { return true; } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/InternalAuthorizationService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/InternalAuthorizationService.java deleted file mode 100644 index 6a21e03b042..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/InternalAuthorizationService.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.authz; - -import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.CompositeIndicesRequest; -import org.elasticsearch.action.IndicesRequest; -import org.elasticsearch.action.admin.indices.alias.Alias; -import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; -import org.elasticsearch.action.search.ClearScrollAction; -import org.elasticsearch.action.search.SearchScrollAction; -import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.metadata.AliasOrIndex; -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.search.action.SearchTransportService; -import org.elasticsearch.xpack.security.SecurityTemplateService; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.user.AnonymousUser; -import org.elasticsearch.xpack.security.user.SystemUser; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.user.XPackUser; -import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler; -import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; -import org.elasticsearch.xpack.security.authz.indicesresolver.DefaultIndicesAndAliasesResolver; -import org.elasticsearch.xpack.security.authz.indicesresolver.IndicesAndAliasesResolver; -import org.elasticsearch.xpack.security.authz.permission.ClusterPermission; -import org.elasticsearch.xpack.security.authz.permission.DefaultRole; -import org.elasticsearch.xpack.security.authz.permission.GlobalPermission; -import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.RunAsPermission; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; -import org.elasticsearch.xpack.security.authz.store.RolesStore; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportRequest; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Predicate; - -import static org.elasticsearch.xpack.security.Security.setting; -import static org.elasticsearch.xpack.security.support.Exceptions.authorizationError; - -/** - * - */ -public class InternalAuthorizationService extends AbstractComponent implements AuthorizationService { - - public static final Setting ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING = - Setting.boolSetting(setting("authc.anonymous.authz_exception"), true, Property.NodeScope); - public static final String INDICES_PERMISSIONS_KEY = "_indices_permissions"; - static final String ORIGINATING_ACTION_KEY = "_originating_action_name"; - - private static final Predicate MONITOR_INDEX_PREDICATE = IndexPrivilege.MONITOR.predicate(); - - private final ClusterService clusterService; - private final RolesStore rolesStore; - private final AuditTrail auditTrail; - private final IndicesAndAliasesResolver[] indicesAndAliasesResolvers; - private final AuthenticationFailureHandler authcFailureHandler; - private final ThreadContext threadContext; - private final boolean anonymousAuthzExceptionEnabled; - - @Inject - public InternalAuthorizationService(Settings settings, RolesStore rolesStore, ClusterService clusterService, - AuditTrail auditTrail, AuthenticationFailureHandler authcFailureHandler, - ThreadPool threadPool, IndexNameExpressionResolver nameExpressionResolver) { - super(settings); - this.rolesStore = rolesStore; - this.clusterService = clusterService; - this.auditTrail = auditTrail; - this.indicesAndAliasesResolvers = new IndicesAndAliasesResolver[] { - new DefaultIndicesAndAliasesResolver(this, nameExpressionResolver) - }; - this.authcFailureHandler = authcFailureHandler; - this.threadContext = threadPool.getThreadContext(); - this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings); - } - - @Override - public List authorizedIndicesAndAliases(User user, String action) { - final String[] anonymousRoles = AnonymousUser.enabled() ? AnonymousUser.getRoles() : Strings.EMPTY_ARRAY; - String[] rolesNames = user.roles(); - if (rolesNames.length == 0 && anonymousRoles.length == 0) { - return Collections.emptyList(); - } - - List> predicates = new ArrayList<>(); - for (String roleName : rolesNames) { - Role role = rolesStore.role(roleName); - if (role != null) { - predicates.add(role.indices().allowedIndicesMatcher(action)); - } - } - if (AnonymousUser.is(user) == false) { - for (String roleName : anonymousRoles) { - Role role = rolesStore.role(roleName); - if (role != null) { - predicates.add(role.indices().allowedIndicesMatcher(action)); - } - } - } - Predicate predicate = predicates.stream().reduce(s -> false, (p1, p2) -> p1.or(p2)); - - List indicesAndAliases = new ArrayList<>(); - MetaData metaData = clusterService.state().metaData(); - // TODO: can this be done smarter? I think there are usually more indices/aliases in the cluster then indices defined a roles? - for (Map.Entry entry : metaData.getAliasAndIndexLookup().entrySet()) { - String aliasOrIndex = entry.getKey(); - if (predicate.test(aliasOrIndex)) { - indicesAndAliases.add(aliasOrIndex); - } - } - - if (XPackUser.is(user) == false) { - // we should filter out the .security index from wildcards - if (indicesAndAliases.remove(SecurityTemplateService.SECURITY_INDEX_NAME)) { - logger.debug("removed [{}] from user [{}] list of authorized indices", - SecurityTemplateService.SECURITY_INDEX_NAME, user.principal()); - } - } - return Collections.unmodifiableList(indicesAndAliases); - } - - @Override - public void authorize(Authentication authentication, String action, TransportRequest request) throws ElasticsearchSecurityException { - // prior to doing any authorization lets set the originating action in the context only - setOriginatingAction(action); - - // first we need to check if the user is the system. If it is, we'll just authorize the system access - if (SystemUser.is(authentication.getRunAsUser())) { - if (SystemUser.isAuthorized(action) && SystemUser.is(authentication.getUser())) { - setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); - grant(authentication, action, request); - return; - } - throw denial(authentication, action, request); - } - - // get the roles of the authenticated user, which may be different than the effective - GlobalPermission permission = permission(authentication.getUser().roles()); - - final boolean isRunAs = authentication.getUser() != authentication.getRunAsUser(); - // permission can be null as it might be that the user's role - // is unknown - if (permission == null || permission.isEmpty()) { - if (isRunAs) { - // the request is a run as request so we should call the specific audit event for a denied run as attempt - throw denyRunAs(authentication, action, request); - } else { - throw denial(authentication, action, request); - } - } - - // check if the request is a run as request - if (isRunAs) { - // first we must authorize for the RUN_AS action - RunAsPermission runAs = permission.runAs(); - if (runAs != null && runAs.check(authentication.getRunAsUser().principal())) { - grantRunAs(authentication, action, request); - permission = permission(authentication.getRunAsUser().roles()); - - // permission can be null as it might be that the user's role - // is unknown - if (permission == null || permission.isEmpty()) { - throw denial(authentication, action, request); - } - } else { - throw denyRunAs(authentication, action, request); - } - } - - // first, we'll check if the action is a cluster action. If it is, we'll only check it - // against the cluster permissions - if (ClusterPrivilege.ACTION_MATCHER.test(action)) { - ClusterPermission cluster = permission.cluster(); - // we use the effectiveUser for permission checking since we are running as a user! - if (cluster != null && cluster.check(action, request, authentication)) { - setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); - grant(authentication, action, request); - return; - } - throw denial(authentication, action, request); - } - - // ok... this is not a cluster action, let's verify it's an indices action - if (!IndexPrivilege.ACTION_MATCHER.test(action)) { - throw denial(authentication, action, request); - } - - // some APIs are indices requests that are not actually associated with indices. For example, - // search scroll request, is categorized under the indices context, but doesn't hold indices names - // (in this case, the security check on the indices was done on the search request that initialized - // the scroll... and we rely on the signed scroll id to provide security over this request). - // so we only check indices if indeed the request is an actual IndicesRequest, if it's not, - // we just grant it if it's a scroll, deny otherwise - if (!(request instanceof IndicesRequest) && !(request instanceof CompositeIndicesRequest)) { - if (isScrollRelatedAction(action)) { - //note that clear scroll shard level actions can originate from a clear scroll all, which doesn't require any - //indices permission as it's categorized under cluster. This is why the scroll check is performed - //even before checking if the user has any indices permission. - grant(authentication, action, request); - return; - } - assert false : "only scroll related requests are known indices api that don't support retrieving the indices they relate to"; - throw denial(authentication, action, request); - } - - if (permission.indices() == null || permission.indices().isEmpty()) { - throw denial(authentication, action, request); - } - - ClusterState clusterState = clusterService.state(); - Set indexNames = resolveIndices(authentication, action, request, clusterState); - assert !indexNames.isEmpty() : "every indices request needs to have its indices set thus the resolved indices must not be empty"; - MetaData metaData = clusterState.metaData(); - IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData); - if (!indicesAccessControl.isGranted()) { - throw denial(authentication, action, request); - } else if (indicesAccessControl.getIndexPermissions(SecurityTemplateService.SECURITY_INDEX_NAME) != null - && indicesAccessControl.getIndexPermissions(SecurityTemplateService.SECURITY_INDEX_NAME).isGranted() - && XPackUser.is(authentication.getRunAsUser()) == false - && MONITOR_INDEX_PREDICATE.test(action) == false) { - // only the XPackUser is allowed to work with this index, but we should allow indices monitoring actions through for debugging - // purposes. These monitor requests also sometimes resolve indices concretely and then requests them - // FIXME its not just the XPackUser. We said the elastic user and superusers could access this! - logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]", - authentication.getRunAsUser().principal(), action, SecurityTemplateService.SECURITY_INDEX_NAME); - throw denial(authentication, action, request); - } else { - setIndicesAccessControl(indicesAccessControl); - } - - //if we are creating an index we need to authorize potential aliases created at the same time - if (IndexPrivilege.CREATE_INDEX_MATCHER.test(action)) { - assert request instanceof CreateIndexRequest; - Set aliases = ((CreateIndexRequest) request).aliases(); - if (!aliases.isEmpty()) { - Set aliasesAndIndices = Sets.newHashSet(indexNames); - for (Alias alias : aliases) { - aliasesAndIndices.add(alias.name()); - } - indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData); - if (!indicesAccessControl.isGranted()) { - throw denial(authentication, "indices:admin/aliases", request); - } - // no need to re-add the indicesAccessControl in the context, - // because the create index call doesn't do anything FLS or DLS - } - } - - grant(authentication, action, request); - } - - private void setIndicesAccessControl(IndicesAccessControl accessControl) { - if (threadContext.getTransient(INDICES_PERMISSIONS_KEY) == null) { - threadContext.putTransient(INDICES_PERMISSIONS_KEY, accessControl); - } - } - - private void setOriginatingAction(String action) { - String originatingAction = threadContext.getTransient(ORIGINATING_ACTION_KEY); - if (originatingAction == null) { - threadContext.putTransient(ORIGINATING_ACTION_KEY, action); - } - } - - private GlobalPermission permission(String[] roleNames) { - if (roleNames.length == 0) { - return DefaultRole.INSTANCE; - } - - if (roleNames.length == 1) { - Role role = rolesStore.role(roleNames[0]); - return role == null ? DefaultRole.INSTANCE : GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE).add(role).build(); - } - - // we'll take all the roles and combine their associated permissions - - GlobalPermission.Compound.Builder roles = GlobalPermission.Compound.builder().add(DefaultRole.INSTANCE); - for (String roleName : roleNames) { - Role role = rolesStore.role(roleName); - if (role != null) { - roles.add(role); - } - } - return roles.build(); - } - - private Set resolveIndices(Authentication authentication, String action, TransportRequest request, ClusterState clusterState) { - MetaData metaData = clusterState.metaData(); - for (IndicesAndAliasesResolver resolver : indicesAndAliasesResolvers) { - if (resolver.requestType().isInstance(request)) { - return resolver.resolve(authentication.getRunAsUser(), action, request, metaData); - } - } - assert false : "we should be able to resolve indices for any known request that requires indices privileges"; - throw denial(authentication, action, request); - } - - private static boolean isScrollRelatedAction(String action) { - return action.equals(SearchScrollAction.NAME) || - action.equals(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME) || - action.equals(SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME) || - action.equals(SearchTransportService.QUERY_SCROLL_ACTION_NAME) || - action.equals(SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME) || - action.equals(ClearScrollAction.NAME) || - action.equals(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); - } - - private ElasticsearchSecurityException denial(Authentication authentication, String action, TransportRequest request) { - auditTrail.accessDenied(authentication.getUser(), action, request); - return denialException(authentication, action); - } - - private ElasticsearchSecurityException denyRunAs(Authentication authentication, String action, TransportRequest request) { - auditTrail.runAsDenied(authentication.getUser(), action, request); - return denialException(authentication, action); - } - - private void grant(Authentication authentication, String action, TransportRequest request) { - auditTrail.accessGranted(authentication.getUser(), action, request); - } - - private void grantRunAs(Authentication authentication, String action, TransportRequest request) { - auditTrail.runAsGranted(authentication.getUser(), action, request); - } - - private ElasticsearchSecurityException denialException(Authentication authentication, String action) { - final User user = authentication.getUser(); - // Special case for anonymous user - if (AnonymousUser.enabled() && AnonymousUser.is(user)) { - if (anonymousAuthzExceptionEnabled == false) { - throw authcFailureHandler.authenticationRequired(action, threadContext); - } - } - // check for run as - if (user != authentication.getRunAsUser()) { - return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", action, user.principal(), - authentication.getRunAsUser().principal()); - } - return authorizationError("action [{}] is unauthorized for user [{}]", action, user.principal()); - } - - public static void addSettings(List> settings) { - settings.add(ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING); - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java index 4d287e61204..99806c119ea 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java @@ -22,12 +22,15 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.xpack.security.support.Validation; +import org.elasticsearch.xpack.security.support.MetadataUtils; import org.elasticsearch.xpack.common.xcontent.XContentUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Map; /** * A holder for a Role that contains user-readable information about the Role @@ -39,16 +42,26 @@ public class RoleDescriptor implements ToXContent { private final String[] clusterPrivileges; private final IndicesPrivileges[] indicesPrivileges; private final String[] runAs; + private final Map metadata; public RoleDescriptor(String name, @Nullable String[] clusterPrivileges, @Nullable IndicesPrivileges[] indicesPrivileges, @Nullable String[] runAs) { + this(name, clusterPrivileges, indicesPrivileges, runAs, null); + } + + public RoleDescriptor(String name, + @Nullable String[] clusterPrivileges, + @Nullable IndicesPrivileges[] indicesPrivileges, + @Nullable String[] runAs, + @Nullable Map metadata) { this.name = name; this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY; this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE; this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY; + this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap(); } public String getName() { @@ -67,6 +80,10 @@ public class RoleDescriptor implements ToXContent { return this.runAs; } + public Map getMetadata() { + return metadata; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("Role["); @@ -77,6 +94,8 @@ public class RoleDescriptor implements ToXContent { sb.append(group.toString()).append(","); } sb.append("], runAs=[").append(Strings.arrayToCommaDelimitedString(runAs)); + sb.append("], metadata=["); + MetadataUtils.writeValue(sb, metadata); sb.append("]]"); return sb.toString(); } @@ -91,6 +110,7 @@ public class RoleDescriptor implements ToXContent { if (!name.equals(that.name)) return false; if (!Arrays.equals(clusterPrivileges, that.clusterPrivileges)) return false; if (!Arrays.equals(indicesPrivileges, that.indicesPrivileges)) return false; + if (!metadata.equals(that.getMetadata())) return false; return Arrays.equals(runAs, that.runAs); } @@ -100,16 +120,18 @@ public class RoleDescriptor implements ToXContent { result = 31 * result + Arrays.hashCode(clusterPrivileges); result = 31 * result + Arrays.hashCode(indicesPrivileges); result = 31 * result + Arrays.hashCode(runAs); + result = 31 * result + metadata.hashCode(); return result; } public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - builder.field("cluster", (Object[]) clusterPrivileges); - builder.field("indices", (Object[]) indicesPrivileges); + builder.field(Fields.CLUSTER.getPreferredName(), (Object[]) clusterPrivileges); + builder.field(Fields.INDICES.getPreferredName(), (Object[]) indicesPrivileges); if (runAs != null) { - builder.field("run_as", runAs); + builder.field(Fields.RUN_AS.getPreferredName(), runAs); } + builder.field(Fields.METADATA.getPreferredName(), metadata); return builder.endObject(); } @@ -122,7 +144,8 @@ public class RoleDescriptor implements ToXContent { indicesPrivileges[i] = IndicesPrivileges.createFrom(in); } String[] runAs = in.readStringArray(); - return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs); + Map metadata = in.readMap(); + return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs, metadata); } public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException { @@ -133,6 +156,7 @@ public class RoleDescriptor implements ToXContent { group.writeTo(out); } out.writeStringArray(descriptor.runAs); + out.writeMap(descriptor.metadata); } public static RoleDescriptor parse(String name, BytesReference source) throws IOException { @@ -160,6 +184,7 @@ public class RoleDescriptor implements ToXContent { IndicesPrivileges[] indicesPrivileges = null; String[] clusterPrivileges = null; String[] runAsUsers = null; + Map metadata = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); @@ -169,11 +194,17 @@ public class RoleDescriptor implements ToXContent { runAsUsers = readStringArray(name, parser, true); } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.CLUSTER)) { clusterPrivileges = readStringArray(name, parser, true); + } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.METADATA)) { + if (token != XContentParser.Token.START_OBJECT) { + throw new ElasticsearchParseException( + "expected field [{}] to be of type object, but found [{}] instead", currentFieldName, token); + } + metadata = parser.map(); } else { throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName); } } - return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers); + return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers, metadata); } private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException { @@ -355,9 +386,7 @@ public class RoleDescriptor implements ToXContent { this.indices = in.readStringArray(); this.fields = in.readOptionalStringArray(); this.privileges = in.readStringArray(); - if (in.readBoolean()) { - this.query = new BytesArray(in.readByteArray()); - } + this.query = in.readOptionalBytesReference(); } @Override @@ -365,12 +394,7 @@ public class RoleDescriptor implements ToXContent { out.writeStringArray(indices); out.writeOptionalStringArray(fields); out.writeStringArray(privileges); - if (query != null) { - out.writeBoolean(true); - out.writeByteArray(BytesReference.toBytes(query)); - } else { - out.writeBoolean(false); - } + out.writeOptionalBytesReference(query); } public static class Builder { @@ -424,5 +448,6 @@ public class RoleDescriptor implements ToXContent { ParseField QUERY = new ParseField("query"); ParseField PRIVILEGES = new ParseField("privileges"); ParseField FIELDS = new ParseField("fields"); + ParseField METADATA = new ParseField("metadata"); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java index 7cf8a0e0e83..ad4f0d7823e 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java @@ -14,7 +14,7 @@ import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.cache.query.QueryCache; import org.elasticsearch.indices.IndicesQueryCache; import org.elasticsearch.search.internal.ShardSearchRequest; -import org.elasticsearch.xpack.security.authz.InternalAuthorizationService; +import org.elasticsearch.xpack.security.authz.AuthorizationService; import java.util.HashSet; import java.util.Set; @@ -50,7 +50,7 @@ public final class OptOutQueryCache extends AbstractIndexComponent implements Qu throw new IllegalStateException("opting out of the query cache. current request can't be found"); } IndicesAccessControl indicesAccessControl = context.getThreadContext().getTransient( - InternalAuthorizationService.INDICES_PERMISSIONS_KEY); + AuthorizationService.INDICES_PERMISSIONS_KEY); if (indicesAccessControl == null) { logger.debug("opting out of the query cache. current request doesn't hold indices permissions"); return weight; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java index 2e133336eea..f827fae6b05 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java @@ -22,7 +22,9 @@ import org.apache.lucene.util.BitSet; import org.apache.lucene.util.BitSetIterator; import org.apache.lucene.util.Bits; import org.apache.lucene.util.SparseFixedBitSet; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.common.ParseFieldMatcher; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.LoggerMessageFormat; @@ -43,16 +45,24 @@ import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.shard.IndexSearcherWrapper; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardUtils; -import org.elasticsearch.xpack.security.authz.InternalAuthorizationService; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptContext; +import org.elasticsearch.script.ScriptService; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.accesscontrol.DocumentSubsetReader.DocumentSubsetDirectoryReader; -import org.elasticsearch.xpack.security.SecurityLicenseState; import org.elasticsearch.xpack.security.support.Exceptions; +import org.elasticsearch.xpack.security.user.User; import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; @@ -75,19 +85,22 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { private final Set allowedMetaFields; private final QueryShardContext queryShardContext; private final BitsetFilterCache bitsetFilterCache; - private final SecurityLicenseState securityLicenseState; + private final XPackLicenseState licenseState; private final ThreadContext threadContext; private final ESLogger logger; + private final ScriptService scriptService; public SecurityIndexSearcherWrapper(IndexSettings indexSettings, QueryShardContext queryShardContext, MapperService mapperService, BitsetFilterCache bitsetFilterCache, - ThreadContext threadContext, SecurityLicenseState securityLicenseState) { + ThreadContext threadContext, XPackLicenseState licenseState, + ScriptService scriptService) { + this.scriptService = scriptService; this.logger = Loggers.getLogger(getClass(), indexSettings.getSettings()); this.mapperService = mapperService; this.queryShardContext = queryShardContext; this.bitsetFilterCache = bitsetFilterCache; this.threadContext = threadContext; - this.securityLicenseState = securityLicenseState; + this.licenseState = licenseState; Set allowedMetaFields = new HashSet<>(); allowedMetaFields.addAll(Arrays.asList(MapperService.getAllMetaFields())); @@ -101,7 +114,7 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { @Override protected DirectoryReader wrap(DirectoryReader reader) { - if (securityLicenseState.documentAndFieldLevelSecurityEnabled() == false) { + if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) { return reader; } @@ -124,6 +137,7 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { BooleanQuery.Builder filter = new BooleanQuery.Builder(); for (BytesReference bytesReference : permissions.getQueries()) { QueryShardContext queryShardContext = copyQueryShardContext(this.queryShardContext); + bytesReference = evaluateTemplate(bytesReference); try (XContentParser parser = XContentFactory.xContent(bytesReference).createParser(bytesReference)) { Optional queryBuilder = queryShardContext.newParseContext(parser).parseInnerQueryBuilder(); if (queryBuilder.isPresent()) { @@ -157,7 +171,7 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { @Override protected IndexSearcher wrap(IndexSearcher searcher) throws EngineException { - if (securityLicenseState.documentAndFieldLevelSecurityEnabled() == false) { + if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) { return searcher; } @@ -262,11 +276,55 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { } } + BytesReference evaluateTemplate(BytesReference querySource) throws IOException { + try (XContentParser parser = XContentFactory.xContent(querySource).createParser(querySource)) { + XContentParser.Token token = parser.nextToken(); + if (token != XContentParser.Token.START_OBJECT) { + throw new ElasticsearchParseException("Unexpected token [" + token + "]"); + } + token = parser.nextToken(); + if (token != XContentParser.Token.FIELD_NAME) { + throw new ElasticsearchParseException("Unexpected token [" + token + "]"); + } + if ("template".equals(parser.currentName())) { + token = parser.nextToken(); + if (token != XContentParser.Token.START_OBJECT) { + throw new ElasticsearchParseException("Unexpected token [" + token + "]"); + } + Script script = Script.parse(parser, ParseFieldMatcher.EMPTY); + // Add the user details to the params + Map params = new HashMap<>(); + if (script.getParams() != null) { + params.putAll(script.getParams()); + } + User user = getUser(); + Map userModel = new HashMap<>(); + userModel.put("username", user.principal()); + userModel.put("full_name", user.fullName()); + userModel.put("email", user.email()); + userModel.put("roles", Arrays.asList(user.roles())); + userModel.put("metadata", Collections.unmodifiableMap(user.metadata())); + params.put("_user", userModel); + // Always enforce mustache script lang: + script = new Script(script.getScript(), script.getType(), "mustache", params, script.getContentType()); + ExecutableScript executable = scriptService.executable(script, ScriptContext.Standard.SEARCH, Collections.emptyMap()); + return (BytesReference) executable.run(); + } else { + return querySource; + } + } + } + protected IndicesAccessControl getIndicesAccessControl() { - IndicesAccessControl indicesAccessControl = threadContext.getTransient(InternalAuthorizationService.INDICES_PERMISSIONS_KEY); + IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY); if (indicesAccessControl == null) { throw Exceptions.authorizationError("no indices permissions found"); } return indicesAccessControl; } + + protected User getUser(){ + Authentication authentication = Authentication.getAuthentication(threadContext); + return authentication.getUser(); + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SetSecurityUserProcessor.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SetSecurityUserProcessor.java new file mode 100644 index 00000000000..10bedf00e08 --- /dev/null +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SetSecurityUserProcessor.java @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.authz.accesscontrol; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.ingest.AbstractProcessor; +import org.elasticsearch.ingest.IngestDocument; +import org.elasticsearch.ingest.Processor; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.user.User; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import static org.elasticsearch.ingest.ConfigurationUtils.newConfigurationException; +import static org.elasticsearch.ingest.ConfigurationUtils.readOptionalList; +import static org.elasticsearch.ingest.ConfigurationUtils.readStringProperty; + +/** + * A processor that adds information of the current authenticated user to the document being ingested. + */ +public final class SetSecurityUserProcessor extends AbstractProcessor { + + public static final String TYPE = "set_security_user"; + + private final ThreadContext threadContext; + private final String field; + private final Set properties; + + SetSecurityUserProcessor(String tag, ThreadContext threadContext, String field, Set properties) { + super(tag); + this.threadContext = threadContext; + this.field = field; + this.properties = properties; + } + + @Override + public void execute(IngestDocument ingestDocument) throws Exception { + Authentication authentication = Authentication.getAuthentication(threadContext); + if (authentication == null) { + throw new IllegalStateException("No user authenticated, only use this processor via authenticated user"); + } + User user = authentication.getUser(); + if (user == null) { + throw new IllegalStateException("No user for authentication"); + } + + Map userObject = new HashMap<>(); + for (Property property : properties) { + switch (property) { + case USERNAME: + if (user.principal() != null) { + userObject.put("username", user.principal()); + } + break; + case FULL_NAME: + if (user.fullName() != null) { + userObject.put("full_name", user.fullName()); + } + break; + case EMAIL: + if (user.email() != null) { + userObject.put("email", user.email()); + } + break; + case ROLES: + if (user.roles() != null && user.roles().length != 0) { + userObject.put("roles", Arrays.asList(user.roles())); + } + break; + case METADATA: + if (user.metadata() != null && user.metadata().isEmpty() == false) { + userObject.put("metadata", user.metadata()); + } + break; + default: + throw new UnsupportedOperationException("unsupported property [" + property + "]"); + } + } + ingestDocument.setFieldValue(field, userObject); + } + + @Override + public String getType() { + return TYPE; + } + + String getField() { + return field; + } + + Set getProperties() { + return properties; + } + + public static final class Factory implements Processor.Factory { + + private final ThreadContext threadContext; + + public Factory(ThreadContext threadContext) { + this.threadContext = threadContext; + } + + @Override + public SetSecurityUserProcessor create(Map processorFactories, String tag, + Map config) throws Exception { + String field = readStringProperty(TYPE, tag, config, "field"); + List propertyNames = readOptionalList(TYPE, tag, config, "properties"); + Set properties; + if (propertyNames != null) { + properties = EnumSet.noneOf(Property.class); + for (String propertyName : propertyNames) { + properties.add(Property.parse(tag, propertyName)); + } + } else { + properties = EnumSet.allOf(Property.class); + } + return new SetSecurityUserProcessor(tag, threadContext, field, properties); + } + } + + enum Property { + + USERNAME, + FULL_NAME, + EMAIL, + ROLES, + METADATA; + + static Property parse(String tag, String value) { + try { + return valueOf(value.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + // not using the original exception as its message is confusing + // (e.g. 'No enum constant org.elasticsearch.xpack.security.authz.accesscontrol.SetSecurityUserProcessor.Property.INVALID') + throw newConfigurationException(TYPE, tag, "properties", "Property value [" + value + "] is in valid"); + } + } + + } + +} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java index 5ee3d53cf94..440ad73eab7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java @@ -7,9 +7,7 @@ package org.elasticsearch.xpack.security.authz.indicesresolver; import org.elasticsearch.action.AliasesRequest; import org.elasticsearch.action.CompositeIndicesRequest; -import org.elasticsearch.action.DocumentRequest; import org.elasticsearch.action.IndicesRequest; -import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.metadata.AliasOrIndex; @@ -19,8 +17,8 @@ import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authz.AuthorizationService; +import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.transport.TransportRequest; import java.util.ArrayList; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaRole.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaRole.java index e75551d2f56..b8e106b378c 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaRole.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaRole.java @@ -9,6 +9,7 @@ import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; +import org.elasticsearch.xpack.security.support.MetadataUtils; /** * @@ -20,7 +21,8 @@ public class KibanaRole extends Role { RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build() }; public static final String NAME = "kibana"; - public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null); + public static final RoleDescriptor DESCRIPTOR = + new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA); public static final KibanaRole INSTANCE = new KibanaRole(); private KibanaRole() { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRole.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRole.java index 92aea957958..355a4de6770 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRole.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRole.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.permission; import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; +import org.elasticsearch.xpack.security.support.MetadataUtils; public class KibanaUserRole extends Role { @@ -16,7 +17,8 @@ public class KibanaUserRole extends Role { RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*").privileges("manage", "read", "index", "delete").build() }; public static final String NAME = "kibana_user"; - public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null); + public static final RoleDescriptor DESCRIPTOR = + new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA); public static final KibanaUserRole INSTANCE = new KibanaUserRole(); private KibanaUserRole() { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java index 2f8fab2b850..273142b5612 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java @@ -9,6 +9,7 @@ import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; +import org.elasticsearch.xpack.security.support.MetadataUtils; /** * @@ -19,7 +20,8 @@ public class SuperuserRole extends Role { public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, new String[] { "all" }, new RoleDescriptor.IndicesPrivileges[] { RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()}, - new String[] { "*" }); + new String[] { "*" }, + MetadataUtils.DEFAULT_RESERVED_METADATA); public static final SuperuserRole INSTANCE = new SuperuserRole(); private SuperuserRole() { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java index 9b276267b79..30c9a9cd7de 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.permission; import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; +import org.elasticsearch.xpack.security.support.MetadataUtils; /** * Reserved role for the transport client @@ -17,7 +18,8 @@ public class TransportClientRole extends Role { public static final String NAME = "transport_client"; private static final String[] CLUSTER_PRIVILEGES = new String[] { "transport_client" }; - public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null); + public static final RoleDescriptor DESCRIPTOR = + new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA); public static final TransportClientRole INSTANCE = new TransportClientRole(); private TransportClientRole() { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index b2b9709da56..48182895868 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -21,7 +21,6 @@ public class CompositeRolesStore implements RolesStore { private final NativeRolesStore nativeRolesStore; private final ReservedRolesStore reservedRolesStore; - @Inject public CompositeRolesStore(FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore, ReservedRolesStore reservedRolesStore) { this.fileRolesStore = fileRolesStore; this.nativeRolesStore = nativeRolesStore; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java index 849c1a87890..edb3f02c517 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java @@ -5,31 +5,6 @@ */ package org.elasticsearch.xpack.security.authz.store; -import com.fasterxml.jackson.dataformat.yaml.snakeyaml.error.YAMLException; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.ElasticsearchParseException; -import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.component.AbstractLifecycleComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.logging.ESLogger; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.yaml.YamlXContent; -import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.authc.support.RefreshListener; -import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.IndicesPermission.Group; -import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.support.NoOpLogger; -import org.elasticsearch.xpack.security.support.Validation; -import org.elasticsearch.watcher.FileChangesListener; -import org.elasticsearch.watcher.FileWatcher; -import org.elasticsearch.watcher.ResourceWatcherService; -import org.elasticsearch.xpack.XPackPlugin; - import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -41,6 +16,30 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import com.fasterxml.jackson.dataformat.yaml.snakeyaml.error.YAMLException; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.yaml.YamlXContent; +import org.elasticsearch.env.Environment; +import org.elasticsearch.watcher.FileChangesListener; +import org.elasticsearch.watcher.FileWatcher; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.security.Security; +import org.elasticsearch.xpack.security.authc.support.RefreshListener; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.IndicesPermission.Group; +import org.elasticsearch.xpack.security.authz.permission.Role; +import org.elasticsearch.xpack.security.support.NoOpLogger; +import org.elasticsearch.xpack.security.support.Validation; + import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableMap; @@ -59,7 +58,6 @@ public class FileRolesStore extends AbstractLifecycleComponent implements RolesS private volatile Map permissions; - @Inject public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService) { this(settings, env, watcherService, RefreshListener.NOOP); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index e5d03e24c94..6324b4d8e5a 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -5,6 +5,21 @@ */ package org.elasticsearch.xpack.security.authz.store; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; +import java.util.function.Function; + import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; @@ -29,8 +44,6 @@ import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Provider; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; @@ -43,6 +56,8 @@ import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.threadpool.ThreadPool.Names; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.action.role.ClearRolesCacheRequest; @@ -53,24 +68,7 @@ import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.permission.IndicesPermission.Group; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.client.SecurityClient; -import org.elasticsearch.xpack.security.support.SelfReschedulingRunnable; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.threadpool.ThreadPool.Names; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiFunction; -import java.util.function.Function; +import org.elasticsearch.threadpool.ThreadPool.Cancellable; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xpack.security.Security.setting; @@ -105,23 +103,21 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C public static final String ROLE_DOC_TYPE = "role"; - private final Provider clientProvider; + private final InternalClient client; private final ThreadPool threadPool; private final AtomicReference state = new AtomicReference<>(State.INITIALIZED); private final ConcurrentHashMap roleCache = new ConcurrentHashMap<>(); - private Client client; private SecurityClient securityClient; private int scrollSize; private TimeValue scrollKeepAlive; - private SelfReschedulingRunnable rolesPoller; + private Cancellable pollerCancellable; private volatile boolean securityIndexExists = false; - @Inject - public NativeRolesStore(Settings settings, Provider clientProvider, ThreadPool threadPool) { + public NativeRolesStore(Settings settings, InternalClient client, ThreadPool threadPool) { super(settings); - this.clientProvider = clientProvider; + this.client = client; this.threadPool = threadPool; } @@ -150,7 +146,6 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C public void start() { try { if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { - this.client = clientProvider.get(); this.securityClient = new SecurityClient(client); this.scrollSize = SCROLL_SIZE_SETTING.get(settings); this.scrollKeepAlive = SCROLL_KEEP_ALIVE_SETTING.get(settings); @@ -162,8 +157,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C logger.warn("failed to perform initial poll of roles index [{}]. scheduling again in [{}]", e, SecurityTemplateService.SECURITY_INDEX_NAME, pollInterval); } - rolesPoller = new SelfReschedulingRunnable(poller, threadPool, pollInterval, Names.GENERIC, logger); - rolesPoller.start(); + pollerCancellable = threadPool.scheduleWithFixedDelay(poller, pollInterval, Names.GENERIC); state.set(State.STARTED); } } catch (Exception e) { @@ -175,7 +169,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C public void stop() { if (state.compareAndSet(State.STARTED, State.STOPPING)) { try { - rolesPoller.stop(); + pollerCancellable.cancel(); } finally { state.set(State.STOPPED); } @@ -501,7 +495,6 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C throw new IllegalStateException("can only reset if stopped!!!"); } this.roleCache.clear(); - this.client = null; this.securityIndexExists = false; this.state.set(State.INITIALIZED); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java index 4d2d8cc815a..830db33f78e 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java @@ -5,7 +5,12 @@ */ package org.elasticsearch.xpack.security.authz.store; -import org.elasticsearch.common.inject.Inject; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.authz.RoleDescriptor; @@ -17,12 +22,6 @@ import org.elasticsearch.xpack.security.authz.permission.TransportClientRole; import org.elasticsearch.xpack.security.user.KibanaUser; import org.elasticsearch.xpack.security.user.SystemUser; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - /** * */ @@ -30,7 +29,6 @@ public class ReservedRolesStore implements RolesStore { private final SecurityContext securityContext; - @Inject public ReservedRolesStore(SecurityContext securityContext) { this.securityContext = securityContext; } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java index 5640fc1a2d4..60d0ca39c72 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java @@ -11,7 +11,7 @@ import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.http.netty.NettyHttpRequest; +import org.elasticsearch.http.netty3.Netty3HttpRequest; import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestFilter; @@ -19,8 +19,8 @@ import org.elasticsearch.rest.RestFilterChain; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.pki.PkiRealm; -import org.elasticsearch.xpack.security.SecurityLicenseState; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; import org.elasticsearch.threadpool.ThreadPool; import org.jboss.netty.handler.ssl.SslHandler; @@ -35,19 +35,19 @@ public class SecurityRestFilter extends RestFilter { private final AuthenticationService service; private final ESLogger logger; - private final SecurityLicenseState licenseState; + private final XPackLicenseState licenseState; private final ThreadContext threadContext; private final boolean extractClientCertificate; @Inject public SecurityRestFilter(AuthenticationService service, RestController controller, Settings settings, - ThreadPool threadPool, SecurityLicenseState licenseState) { + ThreadPool threadPool, XPackLicenseState licenseState) { this.service = service; this.licenseState = licenseState; this.threadContext = threadPool.getThreadContext(); controller.registerFilter(this); - boolean ssl = SecurityNettyHttpServerTransport.SSL_SETTING.get(settings); - extractClientCertificate = ssl && SecurityNettyHttpServerTransport.CLIENT_AUTH_SETTING.get(settings).enabled(); + boolean ssl = SecurityNetty3HttpServerTransport.SSL_SETTING.get(settings); + extractClientCertificate = ssl && SecurityNetty3HttpServerTransport.CLIENT_AUTH_SETTING.get(settings).enabled(); logger = Loggers.getLogger(getClass(), settings); } @@ -59,7 +59,7 @@ public class SecurityRestFilter extends RestFilter { @Override public void process(RestRequest request, RestChannel channel, NodeClient client, RestFilterChain filterChain) throws Exception { - if (licenseState.authenticationAndAuthorizationEnabled()) { + if (licenseState.isAuthAllowed()) { // CORS - allow for preflight unauthenticated OPTIONS request if (request.method() != RestRequest.Method.OPTIONS) { if (extractClientCertificate) { @@ -75,8 +75,8 @@ public class SecurityRestFilter extends RestFilter { } static void putClientCertificateInContext(RestRequest request, ThreadContext threadContext, ESLogger logger) throws Exception { - assert request instanceof NettyHttpRequest; - NettyHttpRequest nettyHttpRequest = (NettyHttpRequest) request; + assert request instanceof Netty3HttpRequest; + Netty3HttpRequest nettyHttpRequest = (Netty3HttpRequest) request; SslHandler handler = nettyHttpRequest.getChannel().getPipeline().get(SslHandler.class); assert handler != null; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/AbstractSSLService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/AbstractSSLService.java index 8b1a276da95..69f44f75b32 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/AbstractSSLService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/AbstractSSLService.java @@ -42,9 +42,9 @@ public abstract class AbstractSSLService extends AbstractComponent { private final ConcurrentHashMap sslContexts = new ConcurrentHashMap<>(); private final SSLContextCacheLoader cacheLoader = new SSLContextCacheLoader(); - protected SSLConfiguration globalSSLConfiguration; - protected Environment env; - protected ResourceWatcherService resourceWatcherService; + protected final SSLConfiguration globalSSLConfiguration; + protected final Environment env; + protected final ResourceWatcherService resourceWatcherService; public AbstractSSLService(Settings settings, Environment environment, Global globalSSLConfiguration, ResourceWatcherService resourceWatcherService) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java index 87ab1c528d3..5d85c447f68 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java @@ -5,9 +5,13 @@ */ package org.elasticsearch.xpack.security.ssl; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; +import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.ExtensionsGenerator; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.Time; @@ -24,6 +28,8 @@ import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.SuppressForbidden; @@ -40,6 +46,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.io.Reader; import java.math.BigInteger; @@ -60,6 +67,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.Supplier; class CertUtils { @@ -155,7 +163,7 @@ class CertUtils { } } - static PrivateKey readPrivateKey(Reader reader, char[] keyPassword) throws Exception { + static PrivateKey readPrivateKey(Reader reader, Supplier passwordSupplier) throws Exception { try (PEMParser parser = new PEMParser(reader)) { Object parsed; List list = new ArrayList<>(1); @@ -173,6 +181,7 @@ class CertUtils { PrivateKeyInfo privateKeyInfo; Object parsedObject = list.get(0); if (parsedObject instanceof PEMEncryptedKeyPair) { + char[] keyPassword = passwordSupplier.get(); if (keyPassword == null) { throw new IllegalArgumentException("cannot read encrypted key without a password"); } @@ -195,30 +204,65 @@ class CertUtils { } } - static X509Certificate generateSignedCertificate(boolean resolveHostname, String nodeName, Set addresses, KeyPair keyPair, - Certificate caCert, PrivateKey caPrivKey) throws Exception { + static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair) throws Exception { + return generateSignedCertificate(x500Principal, null, keyPair, null, null, true); + } + + static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair, + X509Certificate caCert, PrivateKey caPrivKey) throws Exception { + return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false); + } + + private static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair, + X509Certificate caCert, PrivateKey caPrivKey, boolean ca) throws Exception { final DateTime notBefore = new DateTime(DateTimeZone.UTC); final DateTime notAfter = notBefore.plusYears(1); - final BigInteger serial = getSerial(); - - X509Certificate x509CACert = (X509Certificate) caCert; - X500Name subject = new X500Name("CN=" + nodeName); - JcaX509v3CertificateBuilder builder = - new JcaX509v3CertificateBuilder(X500Name.getInstance(x509CACert.getIssuerX500Principal().getEncoded()), serial, - new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic()); - + final BigInteger serial = CertUtils.getSerial(); JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); - builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic())); - builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(x509CACert)); - if (addresses.isEmpty() == false) { - builder.addExtension(Extension.subjectAlternativeName, false, getSubjectAlternativeNames(resolveHostname, addresses)); + + X500Name subject = X500Name.getInstance(principal.getEncoded()); + final X500Name issuer; + final AuthorityKeyIdentifier authorityKeyIdentifier; + if (caCert != null) { + if (caCert.getBasicConstraints() < 0) { + throw new IllegalArgumentException("ca certificate is not a CA!"); + } + issuer = X500Name.getInstance(caCert.getIssuerX500Principal().getEncoded()); + authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert); + } else { + issuer = subject; + authorityKeyIdentifier = + extUtils.createAuthorityKeyIdentifier(keyPair.getPublic(), new X500Principal(issuer.toString()), serial); } - ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(caPrivKey); + JcaX509v3CertificateBuilder builder = + new JcaX509v3CertificateBuilder(issuer, serial, + new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic()); + + builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic())); + builder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier); + if (subjectAltNames != null) { + builder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); + } + builder.addExtension(Extension.basicConstraints, ca, new BasicConstraints(ca)); + + PrivateKey signingKey = caPrivKey != null ? caPrivKey : keyPair.getPrivate(); + ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey); X509CertificateHolder certificateHolder = builder.build(signer); return new JcaX509CertificateConverter().getCertificate(certificateHolder); } + static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws Exception { + JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic()); + if (sanList != null) { + ExtensionsGenerator extGen = new ExtensionsGenerator(); + extGen.addExtension(Extension.subjectAlternativeName, false, sanList); + builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); + } + + return builder.build(new JcaContentSignerBuilder("SHA256withRSA").setProvider(CertUtils.BC_PROV).build(keyPair.getPrivate())); + } + static BigInteger getSerial() { SecureRandom random = new SecureRandom(); BigInteger serial = new BigInteger(SERIAL_BIT_LENGTH, random); @@ -226,10 +270,10 @@ class CertUtils { return serial; } - static KeyPair generateKeyPair() throws Exception { + static KeyPair generateKeyPair(int keysize) throws Exception { // generate a private key KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); + keyPairGenerator.initialize(keysize); return keyPairGenerator.generateKeyPair(); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java new file mode 100644 index 00000000000..df1d35b4717 --- /dev/null +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java @@ -0,0 +1,627 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.ssl; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.openssl.PEMEncryptor; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.cli.SettingCommand; +import org.elasticsearch.cli.Terminal; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParseFieldMatcherSupplier; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.env.Environment; +import org.elasticsearch.node.internal.InternalSettingsPreparer; +import org.elasticsearch.xpack.XPackPlugin; + +import javax.security.auth.x500.X500Principal; +import java.io.OutputStream; +import java.io.Reader; +import java.security.cert.Certificate; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * CLI tool to make generation of certificates or certificate requests easier for users + */ +public class CertificateTool extends SettingCommand { + + private static final String AUTO_GEN_CA_DN = "CN=Elastic Certificate Tool Autogenerated CA"; + private static final String DESCRIPTION = "Simplifies certificate creation for use with the Elastic Stack"; + private static final String DEFAULT_CSR_FILE = "csr-bundle.zip"; + private static final String DEFAULT_CERT_FILE = "certificate-bundle.zip"; + private static final Pattern ALLOWED_FILENAME_CHAR_PATTERN = Pattern.compile("([a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]+)"); + private static final int DEFAULT_KEY_SIZE = 2048; + private static final int FILE_EXTENSION_LENGTH = 4; + static final int MAX_FILENAME_LENGTH = 255 - FILE_EXTENSION_LENGTH; + private static final ObjectParser, CertInfoParseContext> PARSER = new ObjectParser<>("certgen"); + static { + ConstructingObjectParser instanceParser = + new ConstructingObjectParser<>("instances", + a -> new CertificateInformation((String) a[0], (List) a[1], (List) a[2])); + instanceParser.declareString(ConstructingObjectParser.constructorArg(), new ParseField("name")); + instanceParser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("ip")); + instanceParser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("dns")); + + PARSER.declareObjectArray(List::addAll, instanceParser, new ParseField("instances")); + } + + private final OptionSpec outputPathSpec; + private final OptionSpec csrSpec; + private final OptionSpec caCertPathSpec; + private final OptionSpec caKeyPathSpec; + private final OptionSpec caPasswordSpec; + private final OptionSpec caDnSpec; + private final OptionSpec keysizeSpec; + private final OptionSpec inputFileSpec; + + CertificateTool() { + super(DESCRIPTION); + outputPathSpec = parser.accepts("out", "path of the zip file that the output should be written to") + .withRequiredArg(); + csrSpec = parser.accepts("csr", "only generate certificate signing requests"); + caCertPathSpec = parser.accepts("cert", "path to an existing ca certificate").availableUnless(csrSpec).withRequiredArg(); + caKeyPathSpec = parser.accepts("key", "path to an existing ca private key") + .availableIf(caCertPathSpec) + .requiredIf(caCertPathSpec) + .withRequiredArg(); + caPasswordSpec = parser.accepts("pass", "password for an existing ca private key or the generated ca private key") + .availableUnless(csrSpec) + .withOptionalArg(); + caDnSpec = parser.accepts("dn", "distinguished name to use for the generated ca. defaults to " + AUTO_GEN_CA_DN) + .availableUnless(caCertPathSpec) + .withRequiredArg(); + keysizeSpec = parser.accepts("keysize", "size in bits of RSA keys").withRequiredArg().ofType(Integer.class); + inputFileSpec = parser.accepts("in", "file containing details of the instances in yaml format").withRequiredArg(); + } + + public static void main(String[] args) throws Exception { + new CertificateTool().main(args, Terminal.DEFAULT); + } + + @Override + protected void execute(Terminal terminal, OptionSet options, Map settings) throws Exception { + Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings); + final boolean csrOnly = options.has(csrSpec); + printIntro(terminal, csrOnly); + final Path outputFile = getOutputFile(terminal, outputPathSpec.value(options), env, csrOnly ? DEFAULT_CSR_FILE : DEFAULT_CERT_FILE); + final String inputFile = inputFileSpec.value(options); + final int keysize = options.has(keysizeSpec) ? keysizeSpec.value(options) : DEFAULT_KEY_SIZE; + if (csrOnly) { + Collection certificateInformations = getCertificateInformationList(terminal, inputFile, env); + generateAndWriteCsrs(outputFile, certificateInformations, keysize); + } else { + final String dn = options.has(caDnSpec) ? caDnSpec.value(options) : AUTO_GEN_CA_DN; + final boolean prompt = options.has(caPasswordSpec); + final char[] keyPass = options.hasArgument(caPasswordSpec) ? caPasswordSpec.value(options).toCharArray() : null; + CAInfo caInfo = + getCAInfo(terminal, dn, caCertPathSpec.value(options), caKeyPathSpec.value(options), keyPass, prompt, env, keysize); + Collection certificateInformations = getCertificateInformationList(terminal, inputFile, env); + generateAndWriteSignedCertificates(outputFile, certificateInformations, caInfo, keysize); + } + printConclusion(terminal, csrOnly, outputFile); + } + + @Override + protected void printAdditionalHelp(Terminal terminal) { + terminal.println("Simplifies the generation of certificate signing requests and signed"); + terminal.println("certificates. The tool runs interactively unless the 'in' and 'out' parameters"); + terminal.println("are specified. In the interactive mode, the tool will prompt for required"); + terminal.println("values that have not been provided through the use of command line options."); + terminal.println(""); + } + + /** + * Checks for output file in the user specified options or prompts the user for the output file + * + * @param terminal terminal to communicate with a user + * @param outputPath user specified output file, may be {@code null} + * @param env the environment for this tool to resolve files with + * @return a {@link Path} to the output file + */ + static Path getOutputFile(Terminal terminal, String outputPath, Environment env, String defaultFilename) throws IOException { + Path file; + if (outputPath != null) { + file = XPackPlugin.resolveConfigFile(env, Strings.cleanPath(outputPath)); + } else { + file = XPackPlugin.resolveConfigFile(env, defaultFilename); + String input = terminal.readText("Please enter the desired output file [" + file + "]: "); + if (input.isEmpty() == false) { + file = XPackPlugin.resolveConfigFile(env, Strings.cleanPath(input)); + } + } + return file; + } + + /** + * This method handles the collection of information about each instance that is necessary to generate a certificate. The user may + * be prompted or the information can be gathered from a file + * @param terminal the terminal to use for user interaction + * @param inputFile an optional file that will be used to load the instance information + * @param env the environment for this tool to resolve files with + * @return a {@link Collection} of {@link CertificateInformation} that represents each instance + */ + static Collection getCertificateInformationList(Terminal terminal, String inputFile, Environment env) + throws Exception { + if (inputFile != null) { + return parseFile(XPackPlugin.resolveConfigFile(env, inputFile)); + } + Map map = new HashMap<>(); + boolean done = false; + while (done == false) { + String name = terminal.readText("Enter instance name: "); + if (name.isEmpty() == false) { + String ipAddresses = terminal.readText("Enter IP Addresses for instance (comma-separated if more than one) []: "); + String dnsNames = terminal.readText("Enter DNS names for instance (comma-separated if more than one) []: "); + List ipList = Arrays.asList(Strings.splitStringByCommaToArray(ipAddresses)); + List dnsList = Arrays.asList(Strings.splitStringByCommaToArray(dnsNames)); + + CertificateInformation information = new CertificateInformation(name, ipList, dnsList); + List validationErrors = information.validate(); + if (validationErrors.isEmpty()) { + if (map.containsKey(name)) { + terminal.println("Overwriting previously defined instance information [" + name + "]"); + } + map.put(name, information); + } else { + for (String validationError : validationErrors) { + terminal.println(validationError); + } + terminal.println("Skipping entry as invalid values were found"); + } + } else { + terminal.println("A name must be provided"); + } + + String exit = terminal.readText("Would you like to specify another instance? Press 'y' to continue entering instance " + + "information: "); + if ("y".equals(exit) == false) { + done = true; + } + } + return map.values(); + } + + /** + * Parses the input file to retrieve the certificate information + * @param file the file to parse + * @return a collection of certificate information + */ + static Collection parseFile(Path file) throws Exception { + try (Reader reader = Files.newBufferedReader(file)) { + XContentParser xContentParser = XContentType.YAML.xContent().createParser(reader); + return PARSER.parse(xContentParser, new ArrayList<>(), new CertInfoParseContext()); + } + } + + /** + * Generates certificate signing requests and writes them out to the specified file in zip format + * @param outputFile the file to write the output to. This file must not already exist + * @param certInfo the details to use in the certificate signing requests + */ + static void generateAndWriteCsrs(Path outputFile, Collection certInfo, int keysize) throws Exception { + fullyWriteFile(outputFile, (outputStream, pemWriter) -> { + for (CertificateInformation certificateInformation : certInfo) { + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + GeneralNames sanList = getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames); + PKCS10CertificationRequest csr = CertUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList); + + final String dirName = certificateInformation.name.filename + "/"; + ZipEntry zipEntry = new ZipEntry(dirName); + assert zipEntry.isDirectory(); + outputStream.putNextEntry(zipEntry); + + // write csr + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".csr")); + pemWriter.writeObject(csr); + pemWriter.flush(); + outputStream.closeEntry(); + + // write private key + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".key")); + pemWriter.writeObject(keyPair.getPrivate()); + pemWriter.flush(); + outputStream.closeEntry(); + } + }); + } + + /** + * Returns the CA certificate and private key that will be used to sign certificates. These may be specified by the user or + * automatically generated + * + * @param terminal the terminal to use for prompting the user + * @param dn the distinguished name to use for the CA + * @param caCertPath the path to the CA certificate or {@code null} if not provided + * @param caKeyPath the path to the CA private key or {@code null} if not provided + * @param keyPass the password to the private key. If not present and the key is encrypted the user will be prompted + * @param env the environment for this tool to resolve files with + * @return CA cert and private key + */ + static CAInfo getCAInfo(Terminal terminal, String dn, String caCertPath, String caKeyPath, char[] keyPass, boolean prompt, + Environment env, int keysize) throws Exception { + if (caCertPath != null) { + assert caKeyPath != null; + Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(caCertPath), env); + if (certificates.length != 1) { + throw new IllegalArgumentException("expected a single certificate in file [" + caCertPath + "] but found [" + + certificates.length + "]"); + } + Certificate caCert = certificates[0]; + PrivateKey privateKey = readPrivateKey(caKeyPath, keyPass, terminal, env, prompt); + return new CAInfo((X509Certificate) caCert, privateKey); + } + + // generate the CA keys and cert + X500Principal x500Principal = new X500Principal(dn); + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + Certificate caCert = CertUtils.generateCACertificate(x500Principal, keyPair); + final char[] password; + if (prompt) { + password = terminal.readSecret("Enter password for CA private key: "); + } else { + password = keyPass; + } + return new CAInfo((X509Certificate) caCert, keyPair.getPrivate(), true, password); + } + + /** + * Generates signed certificates in PEM format stored in a zip file + * @param outputFile the file that the certificates will be written to. This file must not exist + * @param certificateInformations details for creation of the certificates + * @param caInfo the CA information to sign the certificates with + */ + static void generateAndWriteSignedCertificates(Path outputFile, Collection certificateInformations, + CAInfo caInfo, int keysize) throws Exception { + fullyWriteFile(outputFile, (outputStream, pemWriter) -> { + // write out the CA info first if it was generated + writeCAInfoIfGenerated(outputStream, pemWriter, caInfo); + + for (CertificateInformation certificateInformation : certificateInformations) { + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + Certificate certificate = CertUtils.generateSignedCertificate(certificateInformation.name.x500Principal, + getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames), + keyPair, caInfo.caCert, caInfo.privateKey); + + final String dirName = certificateInformation.name.filename + "/"; + ZipEntry zipEntry = new ZipEntry(dirName); + assert zipEntry.isDirectory(); + outputStream.putNextEntry(zipEntry); + + // write cert + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".crt")); + pemWriter.writeObject(certificate); + pemWriter.flush(); + outputStream.closeEntry(); + + // write private key + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".key")); + pemWriter.writeObject(keyPair.getPrivate()); + pemWriter.flush(); + outputStream.closeEntry(); + } + }); + } + + /** + * This method handles the deletion of a file in the case of a partial write + * @param file the file that is being written to + * @param writer writes the contents of the file + */ + private static void fullyWriteFile(Path file, Writer writer) throws Exception { + boolean success = false; + try (OutputStream outputStream = Files.newOutputStream(file, StandardOpenOption.CREATE_NEW); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8); + JcaPEMWriter pemWriter = new JcaPEMWriter(new OutputStreamWriter(zipOutputStream, StandardCharsets.UTF_8))) { + writer.write(zipOutputStream, pemWriter); + success = true; + } finally { + if (success == false) { + Files.delete(file); + } + } + } + + /** + * This method handles writing out the certificate authority cert and private key if the certificate authority was generated by + * this invocation of the tool + * @param outputStream the output stream to write to + * @param pemWriter the writer for PEM objects + * @param info the certificate authority information + */ + private static void writeCAInfoIfGenerated(ZipOutputStream outputStream, JcaPEMWriter pemWriter, CAInfo info) throws Exception { + if (info.generated) { + final String caDirName = "ca/"; + ZipEntry zipEntry = new ZipEntry(caDirName); + assert zipEntry.isDirectory(); + outputStream.putNextEntry(zipEntry); + outputStream.putNextEntry(new ZipEntry(caDirName + "ca.crt")); + pemWriter.writeObject(info.caCert); + pemWriter.flush(); + outputStream.closeEntry(); + outputStream.putNextEntry(new ZipEntry(caDirName + "ca.key")); + if (info.password != null && info.password.length > 0) { + try { + PEMEncryptor encryptor = new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build(info.password); + pemWriter.writeObject(info.privateKey, encryptor); + } finally { + // we can safely nuke the password chars now + Arrays.fill(info.password, (char) 0); + } + } else { + pemWriter.writeObject(info.privateKey); + } + pemWriter.flush(); + outputStream.closeEntry(); + } + } + + private static void printIntro(Terminal terminal, boolean csr) { + terminal.println("This tool assists you in the generation of X.509 certificates and certificate"); + terminal.println("signing requests for use with SSL in the Elastic stack. Depending on the command"); + terminal.println("line option specified, you may be prompted for the following:"); + terminal.println(""); + terminal.println("* The path to the output file"); + if (csr) { + terminal.println(" * The output file is a zip file containing the certificate signing requests"); + terminal.println(" and private keys for each instance."); + } else { + terminal.println(" * The output file is a zip file containing the signed certificates and"); + terminal.println(" private keys for each instance. If a Certificate Authority was generated,"); + terminal.println(" the certificate and private key will also be included in the output file."); + } + terminal.println("* Information about each instance"); + terminal.println(" * An instance is any piece of the Elastic Stack that requires a SSL certificate."); + terminal.println(" Depending on your configuration, Elasticsearch, Logstash, Kibana, and Beats"); + terminal.println(" may all require a certificate and private key."); + terminal.println(" * The minimum required value for each instance is a name. This can simply be the"); + terminal.println(" hostname, which will be used as the Common Name of the certificate. A full"); + terminal.println(" distinguished name may also be used."); + terminal.println(" * IP addresses and DNS names are optional. Multiple values can be specified as a"); + terminal.println(" comma separated string. If no IP addresses or DNS names are provided, you may"); + terminal.println(" disable hostname verification in your SSL configuration."); + + if (csr == false) { + terminal.println("* Certificate Authority private key password"); + terminal.println(" * The password may be left empty if desired."); + } + terminal.println(""); + terminal.println("Let's get started..."); + terminal.println(""); + } + + private static void printConclusion(Terminal terminal, boolean csr, Path outputFile) { + if (csr) { + terminal.println("Certificate signing requests written to " + outputFile); + terminal.println(""); + terminal.println("This file should be properly secured as it contains the private keys for all"); + terminal.println("instances."); + terminal.println(""); + terminal.println("After unzipping the file, there will be a directory for each instance containing"); + terminal.println("the certificate signing request and the private key. Provide the certificate"); + terminal.println("signing requests to your certificate authority. Once you have received the"); + terminal.println("signed certificate, copy the signed certificate, key, and CA certificate to the"); + terminal.println("configuration directory of the Elastic product that they will be used for and"); + terminal.println("follow the SSL configuration instructions in the product guide."); + } else { + terminal.println("Certificates written to " + outputFile); + terminal.println(""); + terminal.println("This file should be properly secured as it contains the private keys for all"); + terminal.println("instances and the certificate authority."); + terminal.println(""); + terminal.println("After unzipping the file, there will be a directory for each instance containing"); + terminal.println("the certificate and private key. Copy the certificate, key, and CA certificate"); + terminal.println("to the configuration directory of the Elastic product that they will be used for"); + terminal.println("and follow the SSL configuration instructions in the product guide."); + terminal.println(""); + terminal.println("For client applications, you may only need to copy the CA certificate and"); + terminal.println("configure the client to trust this certificate."); + } + } + + /** + * Helper method to read a private key and support prompting of user for a key. To avoid passwords being placed as an argument we + * can prompt the user for their password if we encounter an encrypted key. + * @param path the path to the private key + * @param password the password provided by the user or {@code null} + * @param terminal the terminal to use for user interaction + * @param env the environment to resolve files from + * @param prompt whether to prompt the user or not + * @return the {@link PrivateKey} that was read from the file + */ + private static PrivateKey readPrivateKey(String path, char[] password, Terminal terminal, Environment env, boolean prompt) + throws Exception { + AtomicReference passwordReference = new AtomicReference<>(password); + try (Reader reader = Files.newBufferedReader(XPackPlugin.resolveConfigFile(env, path), StandardCharsets.UTF_8)) { + return CertUtils.readPrivateKey(reader, () -> { + if (password != null || prompt == false) { + return password; + } + char[] promptedValue = terminal.readSecret("Enter password for CA private key: "); + passwordReference.set(promptedValue); + return promptedValue; + }); + } finally { + if (passwordReference.get() != null) { + Arrays.fill(passwordReference.get(), (char) 0); + } + } + } + + private static GeneralNames getSubjectAlternativeNamesValue(List ipAddresses, List dnsNames) { + Set generalNameList = new HashSet<>(); + for (String ip : ipAddresses) { + generalNameList.add(new GeneralName(GeneralName.iPAddress, ip)); + } + + for (String dns : dnsNames) { + generalNameList.add(new GeneralName(GeneralName.dNSName, dns)); + } + + if (generalNameList.isEmpty()) { + return null; + } + return new GeneralNames(generalNameList.toArray(new GeneralName[0])); + } + + static class CertificateInformation { + final Name name; + final List ipAddresses; + final List dnsNames; + + CertificateInformation(String name, List ipAddresses, List dnsNames) { + this.name = Name.fromUserProvidedName(name); + this.ipAddresses = ipAddresses == null ? Collections.emptyList() : ipAddresses; + this.dnsNames = dnsNames == null ? Collections.emptyList() : dnsNames; + } + + List validate() { + List errors = new ArrayList<>(); + if (name.error != null) { + errors.add(name.error); + } + for (String ip : ipAddresses) { + if (InetAddresses.isInetAddress(ip) == false) { + errors.add("[" + ip + "] is not a valid IP address"); + } + } + for (String dnsName : dnsNames) { + if (DERIA5String.isIA5String(dnsName) == false) { + errors.add("[" + dnsName + "] is not a valid DNS name"); + } + } + return errors; + } + } + + static class Name { + + final String originalName; + final X500Principal x500Principal; + final String filename; + final String error; + + private Name(String name, X500Principal x500Principal, String filename, String error) { + this.originalName = name; + this.x500Principal = x500Principal; + this.filename = filename; + this.error = error; + } + + static Name fromUserProvidedName(String name) { + if ("ca".equals(name)) { + return new Name(name, null, null, "[ca] may not be used as an instance name"); + } + + final X500Principal principal; + try { + if (name.contains("=")) { + principal = new X500Principal(name); + } else { + principal = new X500Principal("CN=" + name); + } + } catch (IllegalArgumentException e) { + String error = "[" + name + "] could not be converted to a valid DN\n" + e.getMessage() + "\n" + + ExceptionsHelper.stackTrace(e); + return new Name(name, null, null, error); + } + + String filename = attemptToConvertFilename(Strings.cleanPath(name)); + if (filename == null) { + return new Name(name, principal, null, "could not convert [" + name + "] to a valid filename"); + } + return new Name(name, principal, filename, null); + } + + static String attemptToConvertFilename(String name) { + StringBuilder builder = new StringBuilder(); + Matcher matcher = ALLOWED_FILENAME_CHAR_PATTERN.matcher(name); + while (matcher.find()) { + builder.append(matcher.group(1)); + } + + if (builder.length() > MAX_FILENAME_LENGTH) { + return builder.substring(0, MAX_FILENAME_LENGTH); + } + + if (builder.length() > 0) { + return builder.toString(); + } + return null; + } + } + + static class CAInfo { + final X509Certificate caCert; + final PrivateKey privateKey; + final boolean generated; + final char[] password; + + CAInfo(X509Certificate caCert, PrivateKey privateKey) { + this(caCert, privateKey, false, null); + } + + CAInfo(X509Certificate caCert, PrivateKey privateKey, boolean generated, char[] password) { + this.caCert = caCert; + this.privateKey = privateKey; + this.generated = generated; + this.password = password; + } + } + + private static class CertInfoParseContext implements ParseFieldMatcherSupplier { + + private final ParseFieldMatcher parseFieldMatcher; + + CertInfoParseContext() { + this.parseFieldMatcher = new ParseFieldMatcher(true); + } + + @Override + public ParseFieldMatcher getParseFieldMatcher() { + return parseFieldMatcher; + } + } + + private interface Writer { + void write(ZipOutputStream zipOutputStream, JcaPEMWriter pemWriter) throws Exception; + } +} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ClientSSLService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ClientSSLService.java index 634ad12e074..79b03c4444c 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ClientSSLService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ClientSSLService.java @@ -5,27 +5,16 @@ */ package org.elasticsearch.xpack.security.ssl; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; public class ClientSSLService extends AbstractSSLService { - @Inject - public ClientSSLService(Settings settings, Global globalSSLConfiguration) { - super(settings, null, globalSSLConfiguration, null); - } - - @Inject(optional = true) - public void setEnvironment(Environment environment) { - this.env = environment; - } - - @Inject(optional = true) - public void setResourceWatcherService(ResourceWatcherService resourceWatcherService) { - this.resourceWatcherService = resourceWatcherService; + public ClientSSLService(Settings settings, Environment env, Global globalSSLConfiguration, + ResourceWatcherService resourceWatcherService) { + super(settings, env, globalSSLConfiguration, resourceWatcherService); } @Override diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java index 7603574e024..7a5f7c03c02 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; class PEMKeyConfig extends KeyConfig { @@ -48,9 +49,13 @@ class PEMKeyConfig extends KeyConfig { } PrivateKey readPrivateKey(Path keyPath) throws Exception { + char[] password = keyPassword == null ? null : keyPassword.toCharArray(); try (Reader reader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) { - char[] password = keyPassword == null ? null : keyPassword.toCharArray(); - return CertUtils.readPrivateKey(reader, password); + return CertUtils.readPrivateKey(reader, () -> password); + } finally { + if (password != null) { + Arrays.fill(password, (char) 0); + } } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLConfiguration.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLConfiguration.java index dea26e304ae..84aa8a853eb 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLConfiguration.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLConfiguration.java @@ -5,13 +5,6 @@ */ package org.elasticsearch.xpack.security.ssl; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Setting.Property; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.settings.SettingsModule; -import org.elasticsearch.common.unit.TimeValue; - import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import java.util.Arrays; @@ -21,6 +14,11 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Function; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; + import static org.elasticsearch.xpack.security.Security.setting; import static org.elasticsearch.xpack.security.support.OptionalSettings.createInt; import static org.elasticsearch.xpack.security.support.OptionalSettings.createString; @@ -181,7 +179,6 @@ public abstract class SSLConfiguration { * * @param settings the global settings to build the SSL configuration from */ - @Inject public Global(Settings settings) { this.keyConfig = createGlobalKeyConfig(settings); this.trustConfig = createGlobalTrustConfig(settings, keyConfig); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLModule.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLModule.java deleted file mode 100644 index 57cb5c4cf39..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/SSLModule.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.ssl; - -import org.elasticsearch.common.inject.util.Providers; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; -import org.elasticsearch.xpack.security.support.AbstractSecurityModule; - -/** - * - */ -public class SSLModule extends AbstractSecurityModule { - - public SSLModule(Settings settings) { - super(settings); - } - - @Override - protected void configure(boolean clientMode) { - bind(Global.class).asEagerSingleton(); - bind(ClientSSLService.class).asEagerSingleton(); - if (clientMode) { - bind(ServerSSLService.class).toProvider(Providers.of(null)); - } else { - bind(ServerSSLService.class).asEagerSingleton(); - } - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ServerSSLService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ServerSSLService.java index 84196117a2d..254f6870e4a 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ServerSSLService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/ServerSSLService.java @@ -5,15 +5,13 @@ */ package org.elasticsearch.xpack.security.ssl; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; public class ServerSSLService extends AbstractSSLService { - @Inject public ServerSSLService(Settings settings, Environment environment, Global globalSSLConfiguration, ResourceWatcherService resourceWatcherService) { super(settings, environment, globalSSLConfiguration, resourceWatcherService); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/MetadataUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/MetadataUtils.java new file mode 100644 index 00000000000..03715aec196 --- /dev/null +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/MetadataUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.support; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public class MetadataUtils { + + public static final String RESERVED_PREFIX = "_"; + public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved"; + public static final Map DEFAULT_RESERVED_METADATA = Collections.singletonMap(RESERVED_METADATA_KEY, true); + + private MetadataUtils() {} + + public static void writeValue(StringBuilder sb, Object object) { + if (object instanceof Map) { + sb.append("{"); + for (Map.Entry entry : ((Map)object).entrySet()) { + sb.append(entry.getKey()).append("="); + writeValue(sb, entry.getValue()); + } + sb.append("}"); + + } else if (object instanceof Collection) { + sb.append("["); + boolean first = true; + for (Object item : (Collection) object) { + if (!first) { + sb.append(","); + } + writeValue(sb, item); + first = false; + } + sb.append("]"); + } else if (object.getClass().isArray()) { + sb.append("["); + for (int i = 0; i < Array.getLength(object); i++) { + if (i != 0) { + sb.append(","); + } + writeValue(sb, Array.get(object, i)); + } + sb.append("]"); + } else { + sb.append(object); + } + } + + public static void verifyNoReservedMetadata(Map metadata) { + for (String key : metadata.keySet()) { + if (key.startsWith(RESERVED_PREFIX)) { + throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses"); + } + } + } + + public static boolean containsReservedMetadata(Map metadata) { + for (String key : metadata.keySet()) { + if (key.startsWith(RESERVED_PREFIX)) { + return true; + } + } + return false; + } +} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/SelfReschedulingRunnable.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/SelfReschedulingRunnable.java deleted file mode 100644 index d30a0897deb..00000000000 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/SelfReschedulingRunnable.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.support; - -import org.elasticsearch.common.logging.ESLogger; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.util.concurrent.AbstractRunnable; -import org.elasticsearch.common.util.concurrent.FutureUtils; -import org.elasticsearch.threadpool.ThreadPool; - -import java.util.concurrent.ScheduledFuture; - -public class SelfReschedulingRunnable extends AbstractRunnable { - - private final AbstractRunnable runnable; - private final ThreadPool threadPool; - private final TimeValue interval; - private final String executorName; - private final ESLogger logger; - - private ScheduledFuture scheduledFuture = null; - private volatile boolean run = false; - - public SelfReschedulingRunnable(AbstractRunnable runnable, ThreadPool threadPool, TimeValue interval, String executorName, - ESLogger logger) { - this.runnable = runnable; - this.threadPool = threadPool; - this.interval = interval; - this.executorName = executorName; - this.logger = logger; - } - - public synchronized void start() { - if (run != false || scheduledFuture != null) { - throw new IllegalStateException("start should not be called again before calling stop"); - } - run = true; - scheduledFuture = threadPool.schedule(interval, executorName, this); - } - - @Override - public synchronized void onAfter() { - if (run) { - scheduledFuture = threadPool.schedule(interval, executorName, this); - } - } - - @Override - public void onFailure(Exception e) { - logger.warn("failed to run scheduled task", e); - } - - @Override - protected void doRun() throws Exception { - if (run) { - runnable.run(); - } - } - - public synchronized void stop() { - if (run == false) { - throw new IllegalStateException("stop called but not started or stop called twice"); - } - run = false; - FutureUtils.cancel(scheduledFuture); - scheduledFuture = null; - } - - // package private for testing - ScheduledFuture getScheduledFuture() { - return scheduledFuture; - } -} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportService.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportService.java index e6026d37464..686770c2011 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportService.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportService.java @@ -14,8 +14,8 @@ import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationUtils; import org.elasticsearch.xpack.security.authz.accesscontrol.RequestContext; -import org.elasticsearch.xpack.security.SecurityLicenseState; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; @@ -34,9 +34,9 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; -import static org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport.CLIENT_AUTH_SETTING; -import static org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport.PROFILE_CLIENT_AUTH_SETTING; -import static org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport.SSL_SETTING; +import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.CLIENT_AUTH_SETTING; +import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.PROFILE_CLIENT_AUTH_SETTING; +import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.SSL_SETTING; public class SecurityServerTransportService extends TransportService { @@ -46,7 +46,7 @@ public class SecurityServerTransportService extends TransportService { protected final AuthorizationService authzService; protected final SecurityActionMapper actionMapper; protected final ClientTransportFilter clientFilter; - protected final SecurityLicenseState licenseState; + protected final XPackLicenseState licenseState; protected final Map profileFilters; @@ -56,7 +56,7 @@ public class SecurityServerTransportService extends TransportService { AuthorizationService authzService, SecurityActionMapper actionMapper, ClientTransportFilter clientTransportFilter, - SecurityLicenseState licenseState) { + XPackLicenseState licenseState) { super(settings, transport, threadPool); this.authcService = authcService; this.authzService = authzService; @@ -110,7 +110,7 @@ public class SecurityServerTransportService extends TransportService { } protected Map initializeProfileFilters() { - if (!(transport instanceof SecurityNettyTransport)) { + if (!(transport instanceof SecurityNetty3Transport)) { return Collections.singletonMap(TransportSettings.DEFAULT_PROFILE, new ServerTransportFilter.NodeProfile(authcService, authzService, actionMapper, threadPool.getThreadContext(), false)); } @@ -120,7 +120,7 @@ public class SecurityServerTransportService extends TransportService { for (Map.Entry entry : profileSettingsMap.entrySet()) { Settings profileSettings = entry.getValue(); - final boolean profileSsl = SecurityNettyTransport.profileSsl(profileSettings, settings); + final boolean profileSsl = SecurityNetty3Transport.profileSsl(profileSettings, settings); final boolean clientAuth = PROFILE_CLIENT_AUTH_SETTING.get(profileSettings, settings).enabled(); final boolean extractClientCert = profileSsl && clientAuth; String type = entry.getValue().get(SETTING_NAME, "node"); @@ -155,11 +155,11 @@ public class SecurityServerTransportService extends TransportService { protected final String action; protected final TransportRequestHandler handler; private final Map profileFilters; - private final SecurityLicenseState licenseState; + private final XPackLicenseState licenseState; private final ThreadContext threadContext; public ProfileSecuredRequestHandler(String action, TransportRequestHandler handler, - Map profileFilters, SecurityLicenseState licenseState, + Map profileFilters, XPackLicenseState licenseState, ThreadContext threadContext) { this.action = action; this.handler = handler; @@ -171,7 +171,7 @@ public class SecurityServerTransportService extends TransportService { @Override public void messageReceived(T request, TransportChannel channel, Task task) throws Exception { try (ThreadContext.StoredContext ctx = threadContext.newStoredContext()) { - if (licenseState.authenticationAndAuthorizationEnabled()) { + if (licenseState.isAuthAllowed()) { String profile = channel.getProfileName(); ServerTransportFilter filter = profileFilters.get(profile); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java index ebc923e6ad8..bc17f7fe4ea 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java @@ -5,26 +5,26 @@ */ package org.elasticsearch.xpack.security.transport; -import org.elasticsearch.common.logging.ESLogger; -import org.elasticsearch.common.logging.Loggers; -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.transport.TcpTransportChannel; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.action.SecurityActionMapper; -import org.elasticsearch.xpack.security.authc.AuthenticationService; -import org.elasticsearch.xpack.security.authc.pki.PkiRealm; -import org.elasticsearch.xpack.security.authz.AuthorizationService; -import org.elasticsearch.transport.DelegatingTransportChannel; -import org.elasticsearch.transport.TransportChannel; -import org.elasticsearch.transport.TransportRequest; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.handler.ssl.SslHandler; - import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.transport.DelegatingTransportChannel; +import org.elasticsearch.transport.TcpTransportChannel; +import org.elasticsearch.transport.TransportChannel; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.security.action.SecurityActionMapper; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authc.AuthenticationService; +import org.elasticsearch.xpack.security.authc.pki.PkiRealm; +import org.elasticsearch.xpack.security.authz.AuthorizationService; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.handler.ssl.SslHandler; + import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError; /** diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java index ed0a27caf44..9cb025e81d9 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java @@ -16,9 +16,9 @@ import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.TransportAddress; -import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.SecurityLicenseState; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.transport.TransportSettings; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import java.net.InetAddress; import java.util.ArrayList; @@ -87,8 +87,8 @@ public class IPFilter { } }; - private final AuditTrail auditTrail; - private final SecurityLicenseState licenseState; + private final AuditTrailService auditTrail; + private final XPackLicenseState licenseState; private final boolean alwaysAllowBoundAddresses; private final ESLogger logger; @@ -105,8 +105,8 @@ public class IPFilter { private final SetOnce> profileBoundAddress = new SetOnce<>(); @Inject - public IPFilter(final Settings settings, AuditTrail auditTrail, ClusterSettings clusterSettings, - SecurityLicenseState licenseState) { + public IPFilter(final Settings settings, AuditTrailService auditTrail, ClusterSettings clusterSettings, + XPackLicenseState licenseState) { this.logger = Loggers.getLogger(getClass(), settings); this.auditTrail = auditTrail; this.licenseState = licenseState; @@ -173,7 +173,7 @@ public class IPFilter { } public boolean accept(String profile, InetAddress peerAddress) { - if (licenseState.ipFilteringEnabled() == false) { + if (licenseState.isIpFilteringAllowed() == false) { return true; } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/HandshakeWaitingHandler.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandler.java similarity index 97% rename from elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/HandshakeWaitingHandler.java rename to elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandler.java index c1ffc1fe1a1..5af88b8c6f6 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/HandshakeWaitingHandler.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandler.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.transport.netty; +package org.elasticsearch.xpack.security.transport.netty3; import org.elasticsearch.common.logging.ESLogger; import org.jboss.netty.channel.ChannelFuture; @@ -27,7 +27,7 @@ import java.util.Queue; * itself from the pipeline. * * NOTE: This class assumes that the transport will not use a closed channel again or attempt to reconnect, which - * is the way that NettyTransport currently works + * is the way that Netty3Transport currently works */ public class HandshakeWaitingHandler extends SimpleChannelHandler { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/IPFilterNettyUpstreamHandler.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/IPFilterNetty3UpstreamHandler.java similarity index 83% rename from elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/IPFilterNettyUpstreamHandler.java rename to elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/IPFilterNetty3UpstreamHandler.java index 86cadb1e189..bf10ad80cd7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/IPFilterNettyUpstreamHandler.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/IPFilterNetty3UpstreamHandler.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.transport.netty; +package org.elasticsearch.xpack.security.transport.netty3; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.jboss.netty.channel.ChannelEvent; @@ -17,12 +17,12 @@ import java.net.InetSocketAddress; * */ @ChannelHandler.Sharable -public class IPFilterNettyUpstreamHandler extends IpFilteringHandlerImpl { +public class IPFilterNetty3UpstreamHandler extends IpFilteringHandlerImpl { private final IPFilter filter; private final String profile; - public IPFilterNettyUpstreamHandler(IPFilter filter, String profile) { + public IPFilterNetty3UpstreamHandler(IPFilter filter, String profile) { this.filter = filter; this.profile = profile; } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/SecurityNettyHttpServerTransport.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3HttpServerTransport.java similarity index 86% rename from elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/SecurityNettyHttpServerTransport.java rename to elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3HttpServerTransport.java index bf76ed25999..2ce3cbf38b3 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/SecurityNettyHttpServerTransport.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3HttpServerTransport.java @@ -3,17 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.transport.netty; +package org.elasticsearch.xpack.security.transport.netty3; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.settings.SettingsModule; import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.http.netty.NettyHttpServerTransport; -import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; +import org.elasticsearch.http.netty3.Netty3HttpServerTransport; import org.elasticsearch.xpack.security.ssl.ServerSSLService; import org.elasticsearch.xpack.security.transport.SSLClientAuth; import org.elasticsearch.xpack.security.transport.filter.IPFilter; @@ -26,7 +24,6 @@ import org.jboss.netty.handler.ssl.SslHandler; import javax.net.ssl.SSLEngine; -import java.util.Collections; import java.util.List; import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_COMPRESSION; @@ -37,7 +34,7 @@ import static org.elasticsearch.xpack.security.transport.SSLExceptionHelper.isNo /** * */ -public class SecurityNettyHttpServerTransport extends NettyHttpServerTransport { +public class SecurityNetty3HttpServerTransport extends Netty3HttpServerTransport { public static final boolean SSL_DEFAULT = false; public static final String CLIENT_AUTH_DEFAULT = SSLClientAuth.NO.name(); @@ -55,8 +52,8 @@ public class SecurityNettyHttpServerTransport extends NettyHttpServerTransport { private final Settings sslSettings; @Inject - public SecurityNettyHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, IPFilter ipFilter, - ServerSSLService sslService, ThreadPool threadPool) { + public SecurityNetty3HttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, IPFilter ipFilter, + ServerSSLService sslService, ThreadPool threadPool) { super(settings, networkService, bigArrays, threadPool); this.ipFilter = ipFilter; this.ssl = SSL_SETTING.get(settings); @@ -109,7 +106,7 @@ public class SecurityNettyHttpServerTransport extends NettyHttpServerTransport { private final SSLClientAuth clientAuth; - public HttpSslChannelPipelineFactory(NettyHttpServerTransport transport) { + public HttpSslChannelPipelineFactory(Netty3HttpServerTransport transport) { super(transport, detailedErrorsEnabled, threadPool.getThreadContext()); clientAuth = CLIENT_AUTH_SETTING.get(settings); } @@ -124,7 +121,7 @@ public class SecurityNettyHttpServerTransport extends NettyHttpServerTransport { pipeline.addFirst("ssl", new SslHandler(engine)); } - pipeline.addFirst("ipfilter", new IPFilterNettyUpstreamHandler(ipFilter, IPFilter.HTTP_PROFILE_NAME)); + pipeline.addFirst("ipfilter", new IPFilterNetty3UpstreamHandler(ipFilter, IPFilter.HTTP_PROFILE_NAME)); return pipeline; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/SecurityNettyTransport.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java similarity index 92% rename from elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/SecurityNettyTransport.java rename to elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java index b11bab1acf9..1d67786e9c4 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty/SecurityNettyTransport.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.transport.netty; +package org.elasticsearch.xpack.security.transport.netty3; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.inject.Inject; @@ -20,7 +20,7 @@ import org.elasticsearch.xpack.security.ssl.ServerSSLService; import org.elasticsearch.xpack.security.transport.SSLClientAuth; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.netty.NettyTransport; +import org.elasticsearch.transport.netty3.Netty3Transport; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelPipeline; @@ -41,10 +41,7 @@ import static org.elasticsearch.xpack.security.Security.settingPrefix; import static org.elasticsearch.xpack.security.transport.SSLExceptionHelper.isCloseDuringHandshakeException; import static org.elasticsearch.xpack.security.transport.SSLExceptionHelper.isNotSslRecordException; -/** - * - */ -public class SecurityNettyTransport extends NettyTransport { +public class SecurityNetty3Transport extends Netty3Transport { public static final String CLIENT_AUTH_DEFAULT = SSLClientAuth.REQUIRED.name(); public static final boolean SSL_DEFAULT = false; @@ -82,10 +79,10 @@ public class SecurityNettyTransport extends NettyTransport { private final boolean ssl; @Inject - public SecurityNettyTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, - @Nullable IPFilter authenticator, @Nullable ServerSSLService serverSSLService, - ClientSSLService clientSSLService, NamedWriteableRegistry namedWriteableRegistry, - CircuitBreakerService circuitBreakerService) { + public SecurityNetty3Transport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, + @Nullable IPFilter authenticator, @Nullable ServerSSLService serverSSLService, + ClientSSLService clientSSLService, NamedWriteableRegistry namedWriteableRegistry, + CircuitBreakerService circuitBreakerService) { super(settings, threadPool, networkService, bigArrays, namedWriteableRegistry, circuitBreakerService); this.authenticator = authenticator; this.ssl = SSL_SETTING.get(settings); @@ -148,7 +145,7 @@ public class SecurityNettyTransport extends NettyTransport { private final Settings profileSettings; - public SslServerChannelPipelineFactory(NettyTransport nettyTransport, String name, Settings settings, Settings profileSettings) { + public SslServerChannelPipelineFactory(Netty3Transport nettyTransport, String name, Settings settings, Settings profileSettings) { super(nettyTransport, name, settings); this.profileSettings = profileSettings; } @@ -172,7 +169,7 @@ public class SecurityNettyTransport extends NettyTransport { pipeline.addFirst("ssl", new SslHandler(serverEngine)); } if (authenticator != null) { - pipeline.addFirst("ipfilter", new IPFilterNettyUpstreamHandler(authenticator, name)); + pipeline.addFirst("ipfilter", new IPFilterNetty3UpstreamHandler(authenticator, name)); } return pipeline; } @@ -180,7 +177,7 @@ public class SecurityNettyTransport extends NettyTransport { private class SslClientChannelPipelineFactory extends ClientChannelPipelineFactory { - public SslClientChannelPipelineFactory(NettyTransport transport) { + public SslClientChannelPipelineFactory(Netty3Transport transport) { super(transport); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/user/User.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/user/User.java index b97baaabc44..861a84609ce 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/user/User.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/user/User.java @@ -14,12 +14,11 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.support.MetadataUtils; import java.io.IOException; -import java.lang.reflect.Array; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -28,8 +27,6 @@ import java.util.Map; */ public class User implements ToXContent { - static final String RESERVED_PREFIX = "_"; - private final String username; private final String[] roles; private final User runAs; @@ -53,7 +50,7 @@ public class User implements ToXContent { this.fullName = fullName; this.email = email; this.runAs = null; - verifyNoReservedMetadata(this.username, this.metadata); + verifyNoReservedMetadata(this.metadata); } public User(String username, String[] roles, String fullName, String email, Map metadata, User runAs) { @@ -67,7 +64,7 @@ public class User implements ToXContent { throw new ElasticsearchSecurityException("invalid run_as user"); } this.runAs = runAs; - verifyNoReservedMetadata(this.username, this.metadata); + verifyNoReservedMetadata(this.metadata); } /** @@ -125,7 +122,7 @@ public class User implements ToXContent { sb.append(",fullName=").append(fullName); sb.append(",email=").append(email); sb.append(",metadata="); - append(sb, metadata); + MetadataUtils.writeValue(sb, metadata); if (runAs != null) { sb.append(",runAs=[").append(runAs.toString()).append("]"); } @@ -172,16 +169,12 @@ public class User implements ToXContent { return builder.endObject(); } - void verifyNoReservedMetadata(String principal, Map metadata) { + void verifyNoReservedMetadata(Map metadata) { if (this instanceof ReservedUser) { return; } - for (String key : metadata.keySet()) { - if (key.startsWith(RESERVED_PREFIX)) { - throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses"); - } - } + MetadataUtils.verifyNoReservedMetadata(metadata); } public static User readFrom(StreamInput input) throws IOException { @@ -245,49 +238,10 @@ public class User implements ToXContent { } } - public static void append(StringBuilder sb, Object object) { - if (object == null) { - sb.append((Object) null); - } - if (object instanceof Map) { - sb.append("{"); - for (Map.Entry entry : ((Map)object).entrySet()) { - sb.append(entry.getKey()).append("="); - append(sb, entry.getValue()); - } - sb.append("}"); - - } else if (object instanceof Collection) { - sb.append("["); - boolean first = true; - for (Object item : (Collection) object) { - if (!first) { - sb.append(","); - } - append(sb, item); - first = false; - } - sb.append("]"); - } else if (object.getClass().isArray()) { - sb.append("["); - for (int i = 0; i < Array.getLength(object); i++) { - if (i != 0) { - sb.append(","); - } - append(sb, Array.get(object, i)); - } - sb.append("]"); - } else { - sb.append(object); - } - } - abstract static class ReservedUser extends User { - private static final String RESERVED_KEY = User.RESERVED_PREFIX + "reserved"; - ReservedUser(String username, String... roles) { - super(username, roles, null, null, Collections.singletonMap(RESERVED_KEY, true)); + super(username, roles, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA); } } diff --git a/elasticsearch/x-pack/security/src/main/resources/security-index-template.json b/elasticsearch/x-pack/security/src/main/resources/security-index-template.json index a5557edeb4c..d972f0e2082 100644 --- a/elasticsearch/x-pack/security/src/main/resources/security-index-template.json +++ b/elasticsearch/x-pack/security/src/main/resources/security-index-template.json @@ -86,6 +86,10 @@ }, "run_as" : { "type" : "keyword" + }, + "metadata" : { + "type" : "object", + "dynamic" : true } } }, diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty/NettyHttpMockUtil.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty3/Netty3HttpMockUtil.java similarity index 54% rename from elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty/NettyHttpMockUtil.java rename to elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty3/Netty3HttpMockUtil.java index 7e079a511b9..a27d344c032 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty/NettyHttpMockUtil.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty3/Netty3HttpMockUtil.java @@ -3,20 +3,20 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.http.netty; +package org.elasticsearch.http.netty3; -import org.elasticsearch.transport.netty.OpenChannelsHandler; +import org.elasticsearch.transport.netty3.Netty3OpenChannelsHandler; import static org.mockito.Mockito.mock; -/** Allows setting a mock into NettyHttpServerTransport */ -public class NettyHttpMockUtil { - +/** Allows setting a mock into Netty3HttpServerTransport */ +public class Netty3HttpMockUtil { + /** * We don't really need to start Netty for these tests, but we can't create a pipeline * with a null handler. So we set it to a mock for tests. */ - public static void setOpenChannelsHandlerToMock(NettyHttpServerTransport transport) throws Exception { - transport.serverOpenChannels = mock(OpenChannelsHandler.class); + public static void setOpenChannelsHandlerToMock(Netty3HttpServerTransport transport) throws Exception { + transport.serverOpenChannels = mock(Netty3OpenChannelsHandler.class); } } \ No newline at end of file diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java index 01fc9f06b36..559bc72c3c4 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/AbstractPrivilegeTestCase.java @@ -7,12 +7,12 @@ package org.elasticsearch.integration; import org.apache.http.HttpEntity; import org.apache.http.StatusLine; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import org.apache.http.util.EntityUtils; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; -import org.elasticsearch.client.RestClient; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.security.authc.support.Hasher; import org.elasticsearch.xpack.security.authc.support.SecuredString; @@ -36,14 +36,13 @@ public abstract class AbstractPrivilegeTestCase extends SecurityIntegTestCase { protected void assertAccessIsAllowed(String user, String method, String uri, String body, Map params) throws IOException { - try (Response response = getRestClient().performRequest(method, uri, params, entityOrNull(body), + Response response = getRestClient().performRequest(method, uri, params, entityOrNull(body), new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, - UsernamePasswordToken.basicAuthHeaderValue(user, new SecuredString("passwd".toCharArray()))))) { - StatusLine statusLine = response.getStatusLine(); - String message = String.format(Locale.ROOT, "%s %s: Expected no error got %s %s with body %s", method, uri, - statusLine.getStatusCode(), statusLine.getReasonPhrase(), EntityUtils.toString(response.getEntity())); - assertThat(message, statusLine.getStatusCode(), is(not(greaterThanOrEqualTo(400)))); - } + UsernamePasswordToken.basicAuthHeaderValue(user, new SecuredString("passwd".toCharArray())))); + StatusLine statusLine = response.getStatusLine(); + String message = String.format(Locale.ROOT, "%s %s: Expected no error got %s %s with body %s", method, uri, + statusLine.getStatusCode(), statusLine.getReasonPhrase(), EntityUtils.toString(response.getEntity())); + assertThat(message, statusLine.getStatusCode(), is(not(greaterThanOrEqualTo(400)))); } protected void assertAccessIsAllowed(String user, String method, String uri, String body) throws IOException { @@ -72,7 +71,7 @@ public abstract class AbstractPrivilegeTestCase extends SecurityIntegTestCase { } catch(ResponseException e) { StatusLine statusLine = e.getResponse().getStatusLine(); String message = String.format(Locale.ROOT, "%s %s body %s: Expected 403, got %s %s with body %s", method, uri, body, - statusLine.getStatusCode(), statusLine.getReasonPhrase(), e.getResponseBody()); + statusLine.getStatusCode(), statusLine.getReasonPhrase(), EntityUtils.toString(e.getResponse().getEntity())); assertThat(message, statusLine.getStatusCode(), is(403)); } } @@ -80,7 +79,7 @@ public abstract class AbstractPrivilegeTestCase extends SecurityIntegTestCase { private static HttpEntity entityOrNull(String body) { HttpEntity entity = null; if (body != null) { - entity = new StringEntity(body, RestClient.JSON_CONTENT_TYPE); + entity = new StringEntity(body, ContentType.APPLICATION_JSON); } return entity; } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/BulkUpdateTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/BulkUpdateTests.java index 2c8f083af27..201850afa2c 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/BulkUpdateTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/BulkUpdateTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.integration; import org.apache.http.Header; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import org.apache.http.util.EntityUtils; @@ -13,15 +14,14 @@ import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Response; -import org.elasticsearch.client.RestClient; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.security.Security; +import org.elasticsearch.xpack.security.authc.support.SecuredString; +import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import java.io.IOException; import java.util.Collections; @@ -79,50 +79,42 @@ public class BulkUpdateTests extends SecurityIntegTestCase { UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))); - StringEntity body = new StringEntity("{\"test\":\"test\"}", RestClient.JSON_CONTENT_TYPE); - try (Response response = getRestClient().performRequest("PUT", path, Collections.emptyMap(), body, basicAuthHeader)) { - assertThat(response.getStatusLine().getStatusCode(), equalTo(201)); - } + StringEntity body = new StringEntity("{\"test\":\"test\"}", ContentType.APPLICATION_JSON); + Response response = getRestClient().performRequest("PUT", path, Collections.emptyMap(), body, basicAuthHeader); + assertThat(response.getStatusLine().getStatusCode(), equalTo(201)); - try (Response response = getRestClient().performRequest("GET", path, basicAuthHeader)) { - assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - assertThat(EntityUtils.toString(response.getEntity()), containsString("\"test\":\"test\"")); - } + response = getRestClient().performRequest("GET", path, basicAuthHeader); + assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); + assertThat(EntityUtils.toString(response.getEntity()), containsString("\"test\":\"test\"")); if (randomBoolean()) { flushAndRefresh(); } //update with new field - body = new StringEntity("{\"doc\": {\"not test\": \"not test\"}}", RestClient.JSON_CONTENT_TYPE); - try (Response response = getRestClient().performRequest("POST", path + "/_update", - Collections.emptyMap(), body, basicAuthHeader)) { - assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - } + body = new StringEntity("{\"doc\": {\"not test\": \"not test\"}}", ContentType.APPLICATION_JSON); + response = getRestClient().performRequest("POST", path + "/_update", Collections.emptyMap(), body, basicAuthHeader); + assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - try (Response response = getRestClient().performRequest("GET", path, basicAuthHeader)) { - assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - String responseBody = EntityUtils.toString(response.getEntity()); - assertThat(responseBody, containsString("\"test\":\"test\"")); - assertThat(responseBody, containsString("\"not test\":\"not test\"")); - } + response = getRestClient().performRequest("GET", path, basicAuthHeader); + assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); + String responseBody = EntityUtils.toString(response.getEntity()); + assertThat(responseBody, containsString("\"test\":\"test\"")); + assertThat(responseBody, containsString("\"not test\":\"not test\"")); // this part is important. Without this, the document may be read from the translog which would bypass the bug where // FLS kicks in because the request can't be found and only returns meta fields flushAndRefresh(); body = new StringEntity("{\"update\": {\"_index\": \"index1\", \"_type\": \"type\", \"_id\": \"1\"}}\n" + - "{\"doc\": {\"bulk updated\":\"bulk updated\"}}\n", RestClient.JSON_CONTENT_TYPE); - try (Response response = getRestClient().performRequest("POST", "/_bulk", - Collections.emptyMap(), body, basicAuthHeader)) { - assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - } + "{\"doc\": {\"bulk updated\":\"bulk updated\"}}\n", ContentType.APPLICATION_JSON); + response = getRestClient().performRequest("POST", "/_bulk", Collections.emptyMap(), body, basicAuthHeader); + assertThat(response.getStatusLine().getStatusCode(), equalTo(200)); - try (Response response = getRestClient().performRequest("GET", path, basicAuthHeader)) { - String responseBody = EntityUtils.toString(response.getEntity()); - assertThat(responseBody, containsString("\"test\":\"test\"")); - assertThat(responseBody, containsString("\"not test\":\"not test\"")); - assertThat(responseBody, containsString("\"bulk updated\":\"bulk updated\"")); - } + response = getRestClient().performRequest("GET", path, basicAuthHeader); + responseBody = EntityUtils.toString(response.getEntity()); + assertThat(responseBody, containsString("\"test\":\"test\"")); + assertThat(responseBody, containsString("\"not test\":\"not test\"")); + assertThat(responseBody, containsString("\"bulk updated\":\"bulk updated\"")); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java index 6909db4b6a5..8b2313d3195 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRealmsCacheTests.java @@ -162,13 +162,12 @@ public class ClearRealmsCacheTests extends SecurityIntegTestCase { } static void executeHttpRequest(String path, Map params) throws Exception { - try (Response response = getRestClient().performRequest("POST", path, params, + Response response = getRestClient().performRequest("POST", path, params, new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, - new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))))) { - assertNotNull(response.getEntity()); - assertTrue(EntityUtils.toString(response.getEntity()).contains("cluster_name")); - } + new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray())))); + assertNotNull(response.getEntity()); + assertTrue(EntityUtils.toString(response.getEntity()).contains("cluster_name")); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java index 022ca6d4d07..1c3a9b5e6d5 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java @@ -137,12 +137,11 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { } else { path = "/_xpack/security/role/" + Strings.arrayToCommaDelimitedString(rolesToClear) + "/_clear_cache"; } - try (Response response = getRestClient().performRequest("POST", path, + Response response = getRestClient().performRequest("POST", path, new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, - new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))))) { - assertThat(response.getStatusLine().getStatusCode(), is(RestStatus.OK.getStatus())); - } + new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray())))); + assertThat(response.getStatusLine().getStatusCode(), is(RestStatus.OK.getStatus())); } else { securityClient.prepareClearRolesCache().names(rolesToClear).get(); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java index aed1ca642fb..2c22bb9f5b9 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java @@ -9,11 +9,15 @@ import org.apache.http.message.BasicHeader; import org.elasticsearch.client.ResponseException; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.xpack.MockNetty3Plugin; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.junit.Before; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Locale; import java.util.Map; @@ -130,6 +134,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { .build(); } + @Override + protected Collection> nodePlugins() { + ArrayList> plugins = new ArrayList<>(super.nodePlugins()); + plugins.add(MockNetty3Plugin.class); // for http + return plugins; + } + @Override protected String configRoles() { return super.configRoles() + "\n" + ROLES; diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/LicensingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/LicensingTests.java deleted file mode 100644 index f9f33bde1ca..00000000000 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/LicensingTests.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.integration; - -import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; -import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsIndices; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; -import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; -import org.elasticsearch.action.index.IndexResponse; -import org.elasticsearch.client.Client; -import org.elasticsearch.client.Response; -import org.elasticsearch.client.ResponseException; -import org.elasticsearch.client.transport.NoNodeAvailableException; -import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.component.LifecycleComponent; -import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.inject.Module; -import org.elasticsearch.common.network.NetworkModule; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.Licensing; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; -import org.elasticsearch.rest.RestHandler; -import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.test.SecuritySettingsSource; -import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import org.junit.After; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static java.util.Collections.emptyList; -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - -/** - * - */ -public class LicensingTests extends SecurityIntegTestCase { - public static final String ROLES = - SecuritySettingsSource.DEFAULT_ROLE + ":\n" + - " cluster: [ all ]\n" + - " indices:\n" + - " - names: '*'\n" + - " privileges: [manage]\n" + - " - names: '/.*/'\n" + - " privileges: [write]\n" + - " - names: 'test'\n" + - " privileges: [read]\n" + - " - names: 'test1'\n" + - " privileges: [read]\n" + - "\n" + - "role_a:\n" + - " indices:\n" + - " - names: 'a'\n" + - " privileges: [all]\n" + - "\n" + - "role_b:\n" + - " indices:\n" + - " - names: 'b'\n" + - " privileges: [all]\n"; - - public static final String USERS = - SecuritySettingsSource.CONFIG_STANDARD_USER + - "user_a:{plain}passwd\n" + - "user_b:{plain}passwd\n"; - - public static final String USERS_ROLES = - SecuritySettingsSource.CONFIG_STANDARD_USER_ROLES + - "role_a:user_a,user_b\n" + - "role_b:user_b\n"; - - @Override - protected String configRoles() { - return ROLES; - } - - @Override - protected String configUsers() { - return USERS; - } - - @Override - protected String configUsersRoles() { - return USERS_ROLES; - } - - @Override - public Settings nodeSettings(int nodeOrdinal) { - return Settings.builder().put(super.nodeSettings(nodeOrdinal)) - .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .build(); - } - - @Override - protected Class xpackPluginClass() { - return InternalXPackPlugin.class; - } - - @After - public void resetLicensing() { - enableLicensing(); - } - - public void testEnableDisableBehaviour() throws Exception { - IndexResponse indexResponse = index("test", "type", jsonBuilder() - .startObject() - .field("name", "value") - .endObject()); - assertThat(indexResponse.isCreated(), is(true)); - - - indexResponse = index("test1", "type", jsonBuilder() - .startObject() - .field("name", "value1") - .endObject()); - assertThat(indexResponse.isCreated(), is(true)); - - refresh(); - - Client client = internalCluster().transportClient(); - - disableLicensing(); - - assertElasticsearchSecurityException(() -> client.admin().indices().prepareStats().get()); - assertElasticsearchSecurityException(() -> client.admin().cluster().prepareClusterStats().get()); - assertElasticsearchSecurityException(() -> client.admin().cluster().prepareHealth().get()); - assertElasticsearchSecurityException(() -> client.admin().cluster().prepareNodesStats().get()); - - enableLicensing(randomFrom(OperationMode.values())); - - IndicesStatsResponse indicesStatsResponse = client.admin().indices().prepareStats().get(); - assertNoFailures(indicesStatsResponse); - - ClusterStatsResponse clusterStatsNodeResponse = client.admin().cluster().prepareClusterStats().get(); - assertThat(clusterStatsNodeResponse, notNullValue()); - ClusterStatsIndices indices = clusterStatsNodeResponse.getIndicesStats(); - assertThat(indices, notNullValue()); - assertThat(indices.getIndexCount(), greaterThanOrEqualTo(2)); - - ClusterHealthResponse clusterIndexHealth = client.admin().cluster().prepareHealth().get(); - assertThat(clusterIndexHealth, notNullValue()); - - NodesStatsResponse nodeStats = client.admin().cluster().prepareNodesStats().get(); - assertThat(nodeStats, notNullValue()); - } - - public void testRestAuthenticationByLicenseType() throws Exception { - try (Response response = getRestClient().performRequest("GET", "/")) { - // the default of the licensing tests is basic - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } - - // generate a new license with a mode that enables auth - OperationMode mode = randomFrom(OperationMode.GOLD, OperationMode.TRIAL, OperationMode.PLATINUM, OperationMode.STANDARD); - enableLicensing(mode); - try { - getRestClient().performRequest("GET", "/"); - fail("request should have failed"); - } catch(ResponseException e) { - assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401)); - } - } - - public void testTransportClientAuthenticationByLicenseType() throws Exception { - Settings.Builder builder = Settings.builder() - .put(internalCluster().transportClient().settings()); - // remove user info - builder.remove(Security.USER_SETTING.getKey()); - builder.remove(ThreadContext.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER); - - // basic has no auth - try (TransportClient client = TransportClient.builder().settings(builder).addPlugin(XPackPlugin.class).build()) { - client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); - assertGreenClusterState(client); - } - - // enable a license that enables security - OperationMode mode = randomFrom(OperationMode.GOLD, OperationMode.TRIAL, OperationMode.PLATINUM, OperationMode.STANDARD); - enableLicensing(mode); - - try (TransportClient client = TransportClient.builder().settings(builder).addPlugin(XPackPlugin.class).build()) { - client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); - client.admin().cluster().prepareHealth().get(); - fail("should not have been able to connect to a node!"); - } catch (NoNodeAvailableException e) { - // expected - } - } - - private static void assertElasticsearchSecurityException(ThrowingRunnable runnable) { - ElasticsearchSecurityException ee = expectThrows(ElasticsearchSecurityException.class, runnable); - assertThat(ee.getHeader("es.license.expired.feature"), hasItem(Security.NAME)); - assertThat(ee.status(), is(RestStatus.FORBIDDEN)); - } - - public static void disableLicensing() { - disableLicensing(OperationMode.BASIC); - } - - public static void disableLicensing(OperationMode operationMode) { - for (InternalLicenseeRegistry service : internalCluster().getInstances(InternalLicenseeRegistry.class)) { - service.disable(operationMode); - } - } - - public static void enableLicensing() { - enableLicensing(OperationMode.BASIC); - } - - public static void enableLicensing(OperationMode operationMode) { - for (InternalLicenseeRegistry service : internalCluster().getInstances(InternalLicenseeRegistry.class)) { - service.enable(operationMode); - } - } - - public static class InternalLicensing extends Licensing { - - @Override - public Collection nodeModules() { - return Collections.singletonList(new InternalLicenseModule()); - } - - public InternalLicensing() { - super(Settings.EMPTY); - } - - @Override - public List, ? extends ActionResponse>> getActions() { - return emptyList(); - } - - @Override - public List> getRestHandlers() { - return emptyList(); - } - - @Override - public Collection> nodeServices() { - return Collections.emptyList(); - } - } - - public static class InternalXPackPlugin extends XPackPlugin { - - public InternalXPackPlugin(Settings settings) throws IOException { - super(settings); - licensing = new InternalLicensing(); - } - } - - public static class InternalLicenseModule extends AbstractModule { - @Override - protected void configure() { - bind(InternalLicenseeRegistry.class).asEagerSingleton(); - bind(LicenseeRegistry.class).to(InternalLicenseeRegistry.class); - } - } - - public static class InternalLicenseeRegistry extends AbstractComponent implements LicenseeRegistry { - - private final List licensees = new ArrayList<>(); - - @Inject - public InternalLicenseeRegistry(Settings settings) { - super(settings); - enable(OperationMode.BASIC); - } - - @Override - public void register(Licensee licensee) { - licensees.add(licensee); - enable(OperationMode.BASIC); - } - - void enable(OperationMode operationMode) { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(operationMode, LicenseState.ENABLED)); - } - } - - void disable(OperationMode operationMode) { - for (Licensee licensee : licensees) { - licensee.onChange(new Licensee.Status(operationMode, LicenseState.DISABLED)); - } - } - } -} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java index 6412e8348da..f1334c374de 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ldap/AbstractAdLdapRealmTestCase.java @@ -15,7 +15,7 @@ import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectoryRea import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.test.SecurityIntegTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -166,7 +166,7 @@ public abstract class AbstractAdLdapRealmTestCase extends SecurityIntegTestCase return Settings.builder() .put("xpack.security.ssl.keystore.path", store) .put("xpack.security.ssl.keystore.password", password) - .put(SecurityNettyTransport.HOSTNAME_VERIFICATION_SETTING.getKey(), false) + .put(SecurityNetty3Transport.HOSTNAME_VERIFICATION_SETTING.getKey(), false) .put("xpack.security.ssl.truststore.path", store) .put("xpack.security.ssl.truststore.password", password).build(); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/license/LicensingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/license/LicensingTests.java new file mode 100644 index 00000000000..f6fb771f730 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/license/LicensingTests.java @@ -0,0 +1,221 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import java.util.ArrayList; +import java.util.Collection; + +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsIndices; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; +import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.transport.NoNodeAvailableException; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.network.NetworkModule; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.license.core.License; +import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.SecurityIntegTestCase; +import org.elasticsearch.test.SecuritySettingsSource; +import org.elasticsearch.transport.Transport; +import org.elasticsearch.xpack.MockNetty3Plugin; +import org.elasticsearch.xpack.XPackTransportClient; +import org.elasticsearch.xpack.security.Security; +import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; +import org.junit.Before; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class LicensingTests extends SecurityIntegTestCase { + public static final String ROLES = + SecuritySettingsSource.DEFAULT_ROLE + ":\n" + + " cluster: [ all ]\n" + + " indices:\n" + + " - names: '*'\n" + + " privileges: [manage]\n" + + " - names: '/.*/'\n" + + " privileges: [write]\n" + + " - names: 'test'\n" + + " privileges: [read]\n" + + " - names: 'test1'\n" + + " privileges: [read]\n" + + "\n" + + "role_a:\n" + + " indices:\n" + + " - names: 'a'\n" + + " privileges: [all]\n" + + "\n" + + "role_b:\n" + + " indices:\n" + + " - names: 'b'\n" + + " privileges: [all]\n"; + + public static final String USERS = + SecuritySettingsSource.CONFIG_STANDARD_USER + + "user_a:{plain}passwd\n" + + "user_b:{plain}passwd\n"; + + public static final String USERS_ROLES = + SecuritySettingsSource.CONFIG_STANDARD_USER_ROLES + + "role_a:user_a,user_b\n" + + "role_b:user_b\n"; + + @Override + protected String configRoles() { + return ROLES; + } + + @Override + protected String configUsers() { + return USERS; + } + + @Override + protected String configUsersRoles() { + return USERS_ROLES; + } + + @Override + public Settings nodeSettings(int nodeOrdinal) { + return Settings.builder().put(super.nodeSettings(nodeOrdinal)) + .put(NetworkModule.HTTP_ENABLED.getKey(), true) + .build(); + } + + @Override + protected Collection> nodePlugins() { + ArrayList> plugins = new ArrayList<>(super.nodePlugins()); + plugins.add(MockNetty3Plugin.class); // for http + return plugins; + } + + @Before + public void resetLicensing() { + enableLicensing(); + } + + public void testEnableDisableBehaviour() throws Exception { + IndexResponse indexResponse = index("test", "type", jsonBuilder() + .startObject() + .field("name", "value") + .endObject()); + assertThat(indexResponse.isCreated(), is(true)); + + + indexResponse = index("test1", "type", jsonBuilder() + .startObject() + .field("name", "value1") + .endObject()); + assertThat(indexResponse.isCreated(), is(true)); + + refresh(); + + Client client = internalCluster().transportClient(); + + disableLicensing(); + + assertElasticsearchSecurityException(() -> client.admin().indices().prepareStats().get()); + assertElasticsearchSecurityException(() -> client.admin().cluster().prepareClusterStats().get()); + assertElasticsearchSecurityException(() -> client.admin().cluster().prepareHealth().get()); + assertElasticsearchSecurityException(() -> client.admin().cluster().prepareNodesStats().get()); + + enableLicensing(randomFrom(License.OperationMode.values())); + + IndicesStatsResponse indicesStatsResponse = client.admin().indices().prepareStats().get(); + assertNoFailures(indicesStatsResponse); + + ClusterStatsResponse clusterStatsNodeResponse = client.admin().cluster().prepareClusterStats().get(); + assertThat(clusterStatsNodeResponse, notNullValue()); + ClusterStatsIndices indices = clusterStatsNodeResponse.getIndicesStats(); + assertThat(indices, notNullValue()); + assertThat(indices.getIndexCount(), greaterThanOrEqualTo(2)); + + ClusterHealthResponse clusterIndexHealth = client.admin().cluster().prepareHealth().get(); + assertThat(clusterIndexHealth, notNullValue()); + + NodesStatsResponse nodeStats = client.admin().cluster().prepareNodesStats().get(); + assertThat(nodeStats, notNullValue()); + } + + public void testRestAuthenticationByLicenseType() throws Exception { + Response response = getRestClient().performRequest("GET", "/"); + // the default of the licensing tests is basic + assertThat(response.getStatusLine().getStatusCode(), is(200)); + + // generate a new license with a mode that enables auth + License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL, + License.OperationMode.PLATINUM, License.OperationMode.STANDARD); + enableLicensing(mode); + ResponseException e = expectThrows(ResponseException.class, () -> getRestClient().performRequest("GET", "/")); + assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401)); + } + + public void testTransportClientAuthenticationByLicenseType() throws Exception { + Settings.Builder builder = Settings.builder() + .put(internalCluster().transportClient().settings()); + // remove user info + builder.remove(Security.USER_SETTING.getKey()); + builder.remove(ThreadContext.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER); + + // basic has no auth + try (TransportClient client = new XPackTransportClient(builder.build())) { + client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); + assertGreenClusterState(client); + } + + // enable a license that enables security + License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.TRIAL, + License.OperationMode.PLATINUM, License.OperationMode.STANDARD); + enableLicensing(mode); + + try (TransportClient client = new XPackTransportClient(builder.build())) { + client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); + client.admin().cluster().prepareHealth().get(); + fail("should not have been able to connect to a node!"); + } catch (NoNodeAvailableException e) { + // expected + } + } + + private static void assertElasticsearchSecurityException(ThrowingRunnable runnable) { + ElasticsearchSecurityException ee = expectThrows(ElasticsearchSecurityException.class, runnable); + assertThat(ee.getHeader("es.license.expired.feature"), hasItem(Security.NAME)); + assertThat(ee.status(), is(RestStatus.FORBIDDEN)); + } + + public static void disableLicensing() { + disableLicensing(License.OperationMode.BASIC); + } + + public static void disableLicensing(License.OperationMode operationMode) { + for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { + licenseState.update(operationMode, false); + } + } + + public static void enableLicensing() { + enableLicensing(License.OperationMode.BASIC); + } + + public static void enableLicensing(License.OperationMode operationMode) { + for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { + licenseState.update(operationMode, true); + } + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java new file mode 100644 index 00000000000..21482b1c426 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java @@ -0,0 +1,254 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.elasticsearch.license.core.License.OperationMode; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.monitoring.Monitoring; +import org.elasticsearch.xpack.security.Security; +import org.hamcrest.Matchers; + +import static org.elasticsearch.license.core.License.OperationMode.MISSING; +import static org.elasticsearch.license.core.License.OperationMode.BASIC; +import static org.elasticsearch.license.core.License.OperationMode.GOLD; +import static org.elasticsearch.license.core.License.OperationMode.PLATINUM; +import static org.elasticsearch.license.core.License.OperationMode.STANDARD; +import static org.elasticsearch.license.core.License.OperationMode.TRIAL; +import static org.hamcrest.Matchers.is; + +/** + * Unit tests for the {@link XPackLicenseState} + */ +public class XPackLicenseStateTests extends ESTestCase { + + /** Creates a license state with the given license type and active state, and checks the given method returns expected. */ + void assertAllowed(OperationMode mode, boolean active, Predicate predicate, boolean expected) { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(mode, active); + assertEquals(expected, predicate.test(licenseState)); + } + + /** + * Checks the ack message going from the {@code from} license type to {@code to} license type. + * TODO: check the actual messages, not just the number of them! This was copied from previous license tests... + */ + void assertAckMesssages(String feature, OperationMode from, OperationMode to, int expectedMessages) { + String[] gotMessages = XPackLicenseState.ACKNOWLEDGMENT_MESSAGES.get(feature).apply(from, to); + assertEquals(expectedMessages, gotMessages.length); + } + + static T randomFrom(T[] values, Predicate filter) { + return randomFrom(Arrays.stream(values).filter(filter).collect(Collectors.toList())); + } + + static OperationMode randomMode() { + return randomFrom(OperationMode.values()); + } + + static OperationMode randomTrialStandardGoldOrPlatinumMode() { + return randomFrom(TRIAL, STANDARD, GOLD, PLATINUM); + } + + static OperationMode randomTrialOrPlatinumMode() { + return randomFrom(TRIAL, PLATINUM); + } + + public void testSecurityDefaults() { + XPackLicenseState licenseState = new XPackLicenseState(); + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(true)); + assertThat(licenseState.isAuditingAllowed(), is(true)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(true)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.ALL)); + } + + public void testSecurityBasic() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(BASIC, true); + + assertThat(licenseState.isAuthAllowed(), is(false)); + assertThat(licenseState.isIpFilteringAllowed(), is(false)); + assertThat(licenseState.isAuditingAllowed(), is(false)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NONE)); + } + + public void testSecurityBasicExpired() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(BASIC, false); + + assertThat(licenseState.isAuthAllowed(), is(false)); + assertThat(licenseState.isIpFilteringAllowed(), is(false)); + assertThat(licenseState.isAuditingAllowed(), is(false)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NONE)); + } + + public void testSecurityStandard() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(STANDARD, true); + + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(false)); + assertThat(licenseState.isAuditingAllowed(), is(false)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NATIVE)); + } + + public void testSecurityStandardExpired() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(STANDARD, false); + + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(false)); + assertThat(licenseState.isAuditingAllowed(), is(false)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.NATIVE)); + } + + public void testSecurityGold() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(GOLD, true); + + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(true)); + assertThat(licenseState.isAuditingAllowed(), is(true)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.DEFAULT)); + } + + public void testSecurityGoldExpired() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(GOLD, false); + + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(true)); + assertThat(licenseState.isAuditingAllowed(), is(true)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(false)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.DEFAULT)); + } + + public void testSecurityPlatinum() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(PLATINUM, true); + + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(true)); + assertThat(licenseState.isAuditingAllowed(), is(true)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(true)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(true)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.ALL)); + } + + public void testSecurityPlatinumExpired() { + XPackLicenseState licenseState = new XPackLicenseState(); + licenseState.update(PLATINUM, false); + + assertThat(licenseState.isAuthAllowed(), is(true)); + assertThat(licenseState.isIpFilteringAllowed(), is(true)); + assertThat(licenseState.isAuditingAllowed(), is(true)); + assertThat(licenseState.isStatsAndHealthAllowed(), is(false)); + assertThat(licenseState.isDocumentAndFieldLevelSecurityAllowed(), is(true)); + assertThat(licenseState.allowedRealmType(), Matchers.is(XPackLicenseState.AllowedRealmType.ALL)); + } + + public void testSecurityAckBasicToNotGoldOrStandard() { + OperationMode toMode = randomFrom(OperationMode.values(), mode -> mode != GOLD && mode != STANDARD); + assertAckMesssages(Security.NAME, BASIC, toMode, 0); + } + + public void testSecurityAckAnyToTrialOrPlatinum() { + assertAckMesssages(Security.NAME, randomMode(), randomTrialOrPlatinumMode(), 0); + } + + public void testSecurityAckTrialStandardGoldOrPlatinumToBasic() { + assertAckMesssages(Security.NAME, randomTrialStandardGoldOrPlatinumMode(), BASIC, 3); + } + + public void testSecurityAckAnyToStandard() { + OperationMode from = randomFrom(BASIC, GOLD, PLATINUM, TRIAL); + assertAckMesssages(Security.NAME, from, STANDARD, 4); + } + + public void testSecurityAckBasicStandardTrialOrPlatinumToGold() { + OperationMode from = randomFrom(BASIC, PLATINUM, TRIAL, STANDARD); + assertAckMesssages(Security.NAME, from, GOLD, 2); + } + + public void testMonitoringAckBasicToAny() { + assertAckMesssages(Monitoring.NAME, BASIC, randomMode(), 0); + } + + public void testMonitoringAckAnyToTrialGoldOrPlatinum() { + assertAckMesssages(Monitoring.NAME, randomMode(), randomTrialStandardGoldOrPlatinumMode(), 0); + } + + public void testMonitoringAckNotBasicToBasic() { + OperationMode from = randomFrom(STANDARD, GOLD, PLATINUM, TRIAL); + assertAckMesssages(Monitoring.NAME, from, BASIC, 2); + } + + public void testMonitoringAllowed() { + assertAllowed(randomMode(), true, XPackLicenseState::isMonitoringAllowed, true); + assertAllowed(randomMode(), false, XPackLicenseState::isMonitoringAllowed, false); + } + + public void testMonitoringUpdateRetention() { + OperationMode mode = randomFrom(STANDARD, GOLD, PLATINUM, TRIAL); + assertAllowed(mode, true, XPackLicenseState::isUpdateRetentionAllowed, true); + assertAllowed(BASIC, true, XPackLicenseState::isUpdateRetentionAllowed, false); + assertAllowed(MISSING, false, XPackLicenseState::isUpdateRetentionAllowed, false); + } + + public void testWatcherPlatinumGoldTrial() throws Exception { + assertAllowed(randomFrom(TRIAL, GOLD, PLATINUM), true, XPackLicenseState::isWatcherAllowed, true); + } + + public void testWatcherBasicStandardLicense() throws Exception { + assertAllowed(randomFrom(BASIC, STANDARD), true, XPackLicenseState::isWatcherAllowed, false); + } + + public void testWatcherInactive() { + assertAllowed(randomFrom(BASIC, STANDARD), false, XPackLicenseState::isWatcherAllowed, false); + } + + public void testWatcherInactivePlatinumGoldTrial() throws Exception { + assertAllowed(randomFrom(TRIAL, GOLD, PLATINUM), false, XPackLicenseState::isWatcherAllowed, false); + } + + public void testGraphPlatinumTrial() throws Exception { + assertAllowed(TRIAL, true, XPackLicenseState::isGraphAllowed, true); + assertAllowed(PLATINUM, true, XPackLicenseState::isGraphAllowed, true); + } + + public void testGraphBasic() throws Exception { + assertAllowed(BASIC, true, XPackLicenseState::isGraphAllowed, false); + } + + public void testGraphStandard() throws Exception { + assertAllowed(STANDARD, true, XPackLicenseState::isGraphAllowed, false); + } + + public void testGraphInactiveBasic() { + assertAllowed(BASIC, false, XPackLicenseState::isGraphAllowed, false); + } + + public void testGraphInactivePlatinumTrial() throws Exception { + assertAllowed(TRIAL, false, XPackLicenseState::isGraphAllowed, false); + assertAllowed(PLATINUM, false, XPackLicenseState::isGraphAllowed, false); + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index 5548437e633..80c150d82da 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -16,17 +16,14 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.transport.MockTcpTransportPlugin; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.client.SecurityClient; import org.elasticsearch.test.ESIntegTestCase.SuppressLocalMode; -import org.elasticsearch.test.transport.AssertingLocalTransport; -import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.xpack.XPackClient; import org.elasticsearch.xpack.XPackPlugin; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -37,10 +34,8 @@ import java.net.InetSocketAddress; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -367,7 +362,7 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { final List nodes = nodeInfos.getNodes(); assertTrue("there is at least one node", nodes.size() > 0); NodeInfo ni = randomFrom(nodes); - useSSL = SecurityNettyHttpServerTransport.SSL_SETTING.get(ni.getSettings()); + useSSL = SecurityNetty3HttpServerTransport.SSL_SETTING.get(ni.getSettings()); TransportAddress publishAddress = ni.getHttp().address().publishAddress(); assertEquals(1, publishAddress.uniqueAddressTypeId()); InetSocketAddress address = ((InetSocketTransportAddress) publishAddress).address(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java index 6c0396fa494..eb2990b7691 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java @@ -10,21 +10,22 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.discovery.DiscoveryModule; +import org.elasticsearch.xpack.MockNetty3Plugin; import org.elasticsearch.xpack.monitoring.Monitoring; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase.Scope; import org.elasticsearch.xpack.security.authc.file.FileRealm; import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.security.audit.AuditTrailModule; import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; import org.elasticsearch.xpack.security.authc.support.Hasher; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authz.store.FileRolesStore; import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.test.SecurityTestUtils; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.test.discovery.ClusterDiscoveryConfiguration; import org.elasticsearch.xpack.watcher.Watcher; import org.elasticsearch.xpack.XPackPlugin; @@ -52,7 +53,7 @@ import static org.elasticsearch.xpack.security.test.SecurityTestUtils.writeFile; public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.UnicastZen { public static final Settings DEFAULT_SETTINGS = Settings.builder() - .put("node.mode", "network") + .put(DiscoveryModule.DISCOVERY_TYPE_SETTING.getKey(), "zen") .build(); public static final String DEFAULT_USER_NAME = "test_user"; @@ -127,7 +128,7 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas //TODO: for now isolate security tests from watcher & monitoring (randomize this later) .put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false) .put(XPackPlugin.featureEnabledSetting(Monitoring.NAME), false) - .put(AuditTrailModule.ENABLED_SETTING.getKey(), randomBoolean()) + .put(Security.AUDIT_ENABLED_SETTING.getKey(), randomBoolean()) .put(LoggingAuditTrail.HOST_ADDRESS_SETTING.getKey(), randomBoolean()) .put(LoggingAuditTrail.HOST_NAME_SETTING.getKey(), randomBoolean()) .put(LoggingAuditTrail.NODE_NAME_SETTING.getKey(), randomBoolean()) @@ -160,12 +161,12 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas @Override public Collection> nodePlugins() { - return Arrays.asList(xpackPluginClass()); + return Arrays.asList(xpackPluginClass(), MockNetty3Plugin.class); } @Override public Collection> transportClientPlugins() { - return Collections.>singletonList(xpackPluginClass()); + return nodePlugins(); } protected String configUsers() { @@ -257,18 +258,18 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas Path store = resolveResourcePath(resourcePathToStore); final String sslEnabledSetting = - randomFrom(SecurityNettyTransport.SSL_SETTING.getKey(), SecurityNettyTransport.DEPRECATED_SSL_SETTING.getKey()); + randomFrom(SecurityNetty3Transport.SSL_SETTING.getKey(), SecurityNetty3Transport.DEPRECATED_SSL_SETTING.getKey()); Settings.Builder builder = Settings.builder().put(sslEnabledSetting, sslTransportEnabled); if (transportClient == false) { - builder.put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), false); + builder.put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), false); } if (sslTransportEnabled) { builder.put("xpack.security.ssl.keystore.path", store) .put("xpack.security.ssl.keystore.password", password) - .put(SecurityNettyTransport.HOSTNAME_VERIFICATION_SETTING.getKey(), hostnameVerificationEnabled) - .put(SecurityNettyTransport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING.getKey(), + .put(SecurityNetty3Transport.HOSTNAME_VERIFICATION_SETTING.getKey(), hostnameVerificationEnabled) + .put(SecurityNetty3Transport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING.getKey(), hostnameVerificationResolveNameEnabled); } @@ -285,20 +286,20 @@ public class SecuritySettingsSource extends ClusterDiscoveryConfiguration.Unicas boolean transportClient) { Settings.Builder builder = Settings.builder(); final String sslEnabledSetting = - randomFrom(SecurityNettyTransport.SSL_SETTING.getKey(), SecurityNettyTransport.DEPRECATED_SSL_SETTING.getKey()); + randomFrom(SecurityNetty3Transport.SSL_SETTING.getKey(), SecurityNetty3Transport.DEPRECATED_SSL_SETTING.getKey()); builder.put(sslEnabledSetting, sslTransportEnabled); if (transportClient == false) { - builder.put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), false); + builder.put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), false); } if (sslTransportEnabled) { builder.put("xpack.security.ssl.key.path", resolveResourcePath(keyPath)) .put("xpack.security.ssl.key.password", password) .put("xpack.security.ssl.cert", Strings.arrayToCommaDelimitedString(resolvePathsToString(certificateFiles))) - .put(randomFrom(SecurityNettyTransport.HOSTNAME_VERIFICATION_SETTING.getKey(), - SecurityNettyTransport.DEPRECATED_HOSTNAME_VERIFICATION_SETTING.getKey()), hostnameVerificationEnabled) - .put(SecurityNettyTransport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING.getKey(), + .put(randomFrom(SecurityNetty3Transport.HOSTNAME_VERIFICATION_SETTING.getKey(), + SecurityNetty3Transport.DEPRECATED_HOSTNAME_VERIFICATION_SETTING.getKey()), hostnameVerificationEnabled) + .put(SecurityNetty3Transport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING.getKey(), hostnameVerificationResolveNameEnabled); if (trustedCertificates.isEmpty() == false) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/KnownActionsTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/KnownActionsTests.java index 583cbe9edcb..109e1cd4e3a 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/KnownActionsTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/KnownActionsTests.java @@ -9,7 +9,7 @@ import org.apache.lucene.util.IOUtils; import org.elasticsearch.action.Action; import org.elasticsearch.common.io.PathUtils; import org.elasticsearch.common.io.Streams; -import org.elasticsearch.license.plugin.Licensing; +import org.elasticsearch.license.Licensing; import org.elasticsearch.xpack.security.action.SecurityActionModule; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.SecurityIntegTestCase; diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty/NettyMockUtil.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty3/Netty3MockUtil.java similarity index 60% rename from elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty/NettyMockUtil.java rename to elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty3/Netty3MockUtil.java index 747c4d74fee..0a4f922a2e3 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty/NettyMockUtil.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty3/Netty3MockUtil.java @@ -3,17 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.transport.netty; +package org.elasticsearch.transport.netty3; import static org.mockito.Mockito.mock; -/** Allows setting a mock into NettyTransport */ -public class NettyMockUtil { +/** Allows setting a mock into Netty3Transport */ +public class Netty3MockUtil { /** * We don't really need to start Netty for these tests, but we can't create a pipeline * with a null handler. So we set it to a mock for tests. */ - public static void setOpenChannelsHandlerToMock(NettyTransport transport) throws Exception { - transport.serverOpenChannels = mock(OpenChannelsHandler.class); + public static void setOpenChannelsHandlerToMock(Netty3Transport transport) throws Exception { + transport.serverOpenChannels = mock(Netty3OpenChannelsHandler.class); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java index ae4689fbefb..7d74778f0fb 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityFeatureSetTests.java @@ -8,16 +8,17 @@ package org.elasticsearch.xpack.security; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.XPackFeatureSet; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.Realm; import org.elasticsearch.xpack.security.authc.Realms; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.XPackFeatureSet; -import org.elasticsearch.xpack.security.authz.store.RolesStore; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.junit.Before; @@ -37,28 +38,25 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -/** - * - */ public class SecurityFeatureSetTests extends ESTestCase { private Settings settings; - private SecurityLicenseState licenseState; + private XPackLicenseState licenseState; private Realms realms; private NamedWriteableRegistry namedWriteableRegistry; private IPFilter ipFilter; - private RolesStore rolesStore; + private CompositeRolesStore rolesStore; private AuditTrailService auditTrail; private CryptoService cryptoService; @Before public void init() throws Exception { settings = Settings.builder().put("path.home", createTempDir()).build(); - licenseState = mock(SecurityLicenseState.class); + licenseState = mock(XPackLicenseState.class); realms = mock(Realms.class); namedWriteableRegistry = mock(NamedWriteableRegistry.class); ipFilter = mock(IPFilter.class); - rolesStore = mock(RolesStore.class); + rolesStore = mock(CompositeRolesStore.class); auditTrail = mock(AuditTrailService.class); cryptoService = mock(CryptoService.class); } @@ -72,7 +70,7 @@ public class SecurityFeatureSetTests extends ESTestCase { SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, namedWriteableRegistry, rolesStore, ipFilter, auditTrail, cryptoService); boolean available = randomBoolean(); - when(licenseState.authenticationAndAuthorizationEnabled()).thenReturn(available); + when(licenseState.isAuthAllowed()).thenReturn(available); assertThat(featureSet.available(), is(available)); } @@ -108,7 +106,7 @@ public class SecurityFeatureSetTests extends ESTestCase { public void testUsage() throws Exception { boolean authcAuthzAvailable = randomBoolean(); - when(licenseState.authenticationAndAuthorizationEnabled()).thenReturn(authcAuthzAvailable); + when(licenseState.isAuthAllowed()).thenReturn(authcAuthzAvailable); Settings.Builder settings = Settings.builder().put(this.settings); @@ -116,9 +114,9 @@ public class SecurityFeatureSetTests extends ESTestCase { settings.put("xpack.security.enabled", enabled); final boolean httpSSLEnabled = randomBoolean(); - settings.put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), httpSSLEnabled); + settings.put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), httpSSLEnabled); final boolean transportSSLEnabled = randomBoolean(); - settings.put(SecurityNettyTransport.SSL_SETTING.getKey(), transportSSLEnabled); + settings.put(SecurityNetty3Transport.SSL_SETTING.getKey(), transportSSLEnabled); final boolean auditingEnabled = randomBoolean(); final String[] auditOutputs = randomFrom(new String[] {"logfile"}, new String[] {"index"}, new String[] {"logfile", "index"}); when(auditTrail.usageStats()) diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityLicenseStateTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityLicenseStateTests.java deleted file mode 100644 index 9fd0c4f9aca..00000000000 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityLicenseStateTests.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.license.core.License; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.LicenseState; -import org.elasticsearch.license.plugin.core.Licensee; -import org.elasticsearch.xpack.security.SecurityLicenseState.EnabledRealmType; -import org.elasticsearch.test.ESTestCase; - -import static org.hamcrest.Matchers.is; - -/** - * Unit tests for the {@link SecurityLicenseState} - */ -public class SecurityLicenseStateTests extends ESTestCase { - - public void testDefaults() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(true)); - assertThat(licenseState.auditingEnabled(), is(true)); - assertThat(licenseState.statsAndHealthEnabled(), is(true)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(true)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.ALL)); - } - - public void testBasic() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(License.OperationMode.BASIC, - randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(false)); - assertThat(licenseState.ipFilteringEnabled(), is(false)); - assertThat(licenseState.auditingEnabled(), is(false)); - assertThat(licenseState.statsAndHealthEnabled(), is(true)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.NONE)); - } - - public void testBasicExpired() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(License.OperationMode.BASIC, LicenseState.DISABLED)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(false)); - assertThat(licenseState.ipFilteringEnabled(), is(false)); - assertThat(licenseState.auditingEnabled(), is(false)); - assertThat(licenseState.statsAndHealthEnabled(), is(false)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.NONE)); - } - - public void testStandard() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(OperationMode.STANDARD, - randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(false)); - assertThat(licenseState.auditingEnabled(), is(false)); - assertThat(licenseState.statsAndHealthEnabled(), is(true)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.NATIVE)); - } - - public void testStandardExpired() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(OperationMode.STANDARD, LicenseState.DISABLED)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(false)); - assertThat(licenseState.auditingEnabled(), is(false)); - assertThat(licenseState.statsAndHealthEnabled(), is(false)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.NATIVE)); - } - - public void testGold() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(License.OperationMode.GOLD, - randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(true)); - assertThat(licenseState.auditingEnabled(), is(true)); - assertThat(licenseState.statsAndHealthEnabled(), is(true)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.DEFAULT)); - } - - public void testGoldExpired() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(License.OperationMode.GOLD, LicenseState.DISABLED)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(true)); - assertThat(licenseState.auditingEnabled(), is(true)); - assertThat(licenseState.statsAndHealthEnabled(), is(false)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(false)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.DEFAULT)); - } - - public void testPlatinum() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(License.OperationMode.PLATINUM, - randomBoolean() ? LicenseState.ENABLED : LicenseState.GRACE_PERIOD)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(true)); - assertThat(licenseState.auditingEnabled(), is(true)); - assertThat(licenseState.statsAndHealthEnabled(), is(true)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(true)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.ALL)); - } - - public void testPlatinumExpired() { - SecurityLicenseState licenseState = new SecurityLicenseState(); - licenseState.updateStatus(new Licensee.Status(License.OperationMode.PLATINUM, LicenseState.DISABLED)); - - assertThat(licenseState.authenticationAndAuthorizationEnabled(), is(true)); - assertThat(licenseState.ipFilteringEnabled(), is(true)); - assertThat(licenseState.auditingEnabled(), is(true)); - assertThat(licenseState.statsAndHealthEnabled(), is(false)); - assertThat(licenseState.documentAndFieldLevelSecurityEnabled(), is(true)); - assertThat(licenseState.enabledRealmType(), is(EnabledRealmType.ALL)); - } -} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityLicenseeTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityLicenseeTests.java deleted file mode 100644 index c221c7be83a..00000000000 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityLicenseeTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.license.core.License.OperationMode; -import org.elasticsearch.license.plugin.core.AbstractLicenseeTestCase; -import org.elasticsearch.license.plugin.core.Licensee.Status; -import org.elasticsearch.license.plugin.core.LicenseeRegistry; - -import static org.hamcrest.Matchers.equalTo; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests {@link SecurityLicensee}. - *

- * If you change the behavior of these tests, then it means that licensing changes for Security! - */ -public class SecurityLicenseeTests extends AbstractLicenseeTestCase { - private final SecurityLicenseState securityLicenseState = mock(SecurityLicenseState.class); - private final LicenseeRegistry registry = mock(LicenseeRegistry.class); - - public void testStartsWithoutTribeNode() { - SecurityLicensee licensee = new SecurityLicensee(Settings.EMPTY, registry, securityLicenseState); - - // starting the Licensee start trigger it being registered - licensee.start(); - - verify(registry).register(licensee); - verifyNoMoreInteractions(registry, securityLicenseState); - } - - public void testDoesNotStartWithTribeNode() { - Settings settings = Settings.builder().put("tribe.fake.cluster.name", "notchecked").build(); - SecurityLicensee licensee = new SecurityLicensee(settings, registry, securityLicenseState); - - // starting the Licensee as a tribe node should not trigger it being registered - licensee.start(); - - verifyNoMoreInteractions(registry, securityLicenseState); - } - - public void testOnChangeModifiesSecurityLicenseState() { - Status status = mock(Status.class); - - SecurityLicensee licensee = new SecurityLicensee(Settings.EMPTY, registry, securityLicenseState); - - licensee.onChange(status); - - assertSame(status, licensee.getStatus()); - - verify(securityLicenseState).updateStatus(status); - verifyNoMoreInteractions(registry, securityLicenseState); - } - - public void testAcknowledgementMessagesFromBasicToAnyNotGoldOrStandardIsNoOp() { - assertEmptyAck(OperationMode.BASIC, - randomFrom(OperationMode.values(), mode -> mode != OperationMode.GOLD && mode != OperationMode.STANDARD), - this::buildLicensee); - } - - public void testAcknowledgementMessagesFromAnyToTrialOrPlatinumIsNoOp() { - assertEmptyAck(randomMode(), randomTrialOrPlatinumMode(), this::buildLicensee); - } - - public void testAcknowledgementMessagesFromTrialStandardGoldOrPlatinumToBasicNotesLimits() { - OperationMode from = randomTrialStandardGoldOrPlatinumMode(); - OperationMode to = OperationMode.BASIC; - - String[] messages = ackLicenseChange(from, to, this::buildLicensee); - - // leaving messages up to inspection - assertThat(fromToMessage(from, to), messages.length, equalTo(3)); - } - - public void testAcknowlegmentMessagesFromAnyToStandardNotesLimits() { - OperationMode from = randomFrom(OperationMode.BASIC, OperationMode.GOLD, OperationMode.PLATINUM, OperationMode.TRIAL); - OperationMode to = OperationMode.STANDARD; - - String[] messages = ackLicenseChange(from, to, this::buildLicensee); - - // leaving messages up to inspection - assertThat(fromToMessage(from, to), messages.length, equalTo(4)); - } - - public void testAcknowledgementMessagesFromBasicStandardTrialOrPlatinumToGoldNotesLimits() { - OperationMode from = randomFrom(OperationMode.BASIC, OperationMode.PLATINUM, OperationMode.TRIAL, OperationMode.STANDARD); - String[] messages = ackLicenseChange(from, OperationMode.GOLD, this::buildLicensee); - - // leaving messages up to inspection - assertThat(messages.length, equalTo(2)); - } - - private SecurityLicensee buildLicensee() { - return new SecurityLicensee(Settings.EMPTY, registry, securityLicenseState); - } -} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginEnabledDisabledTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginEnabledDisabledTests.java deleted file mode 100644 index b2e9af0013a..00000000000 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginEnabledDisabledTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security; - -import org.elasticsearch.common.network.NetworkModule; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.integration.LicensingTests; -import org.elasticsearch.xpack.security.transport.SecurityServerTransportService; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; -import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.transport.Transport; -import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.XPackPlugin; -import org.hamcrest.Matcher; -import org.junit.After; -import org.junit.BeforeClass; - -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.not; - -/** - * - */ -public class SecurityPluginEnabledDisabledTests extends SecurityIntegTestCase { - private static boolean enabled; - - @BeforeClass - public static void init() { - enabled = randomBoolean(); - } - - @After - public void cleanup() throws Exception { - // now that on a disabled license we block cluster health/stats and indices stats, we need - // to make sure that after the tests (which disable the license for testing purposes) we - // reenabled the license, so the internal cluster will be cleaned appropriately. - logger.info("cleanup: enabling licensing..."); - LicensingTests.enableLicensing(); - } - @Override - protected Class xpackPluginClass() { - return LicensingTests.InternalXPackPlugin.class; - } - - @Override - protected Settings nodeSettings(int nodeOrdinal) { - logger.info("******* security is {}", enabled ? "enabled" : "disabled"); - return Settings.builder() - .put(super.nodeSettings(nodeOrdinal)) - .put(XPackPlugin.featureEnabledSetting(Security.NAME), enabled) - .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .build(); - } - - @Override - protected Settings transportClientSettings() { - return Settings.builder() - .put(super.transportClientSettings()) - .put(XPackPlugin.featureEnabledSetting(Security.NAME), enabled) - .build(); - } - - public void testTransportEnabledDisabled() throws Exception { - for (TransportService service : internalCluster().getInstances(TransportService.class)) { - Matcher matcher = instanceOf(SecurityServerTransportService.class); - if (!enabled) { - matcher = not(matcher); - } - assertThat(service, matcher); - } - for (Transport transport : internalCluster().getInstances(Transport.class)) { - Matcher matcher = instanceOf(SecurityNettyTransport.class); - if (!enabled) { - matcher = not(matcher); - } - assertThat(transport, matcher); - } - } -} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginTests.java index 2f400a12447..cdbfc30f236 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityPluginTests.java @@ -41,11 +41,10 @@ public class SecurityPluginTests extends SecurityIntegTestCase { } logger.info("executing authorized request to /_xpack infos"); - try (Response response = getRestClient().performRequest("GET", "/_xpack", + Response response = getRestClient().performRequest("GET", "/_xpack", new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, - new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))))) { - assertThat(response.getStatusLine().getStatusCode(), is(OK.getStatus())); - } + new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray())))); + assertThat(response.getStatusLine().getStatusCode(), is(OK.getStatus())); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java index daffe7a159e..e5e71fb5539 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java @@ -9,8 +9,6 @@ import java.io.IOException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.audit.AuditTrailModule; import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.XPackPlugin; @@ -159,14 +157,14 @@ public class SecuritySettingsTests extends ESTestCase { Security.validateAutoCreateIndex(Settings.builder() .put("action.auto_create_index", ".security") - .put(AuditTrailModule.ENABLED_SETTING.getKey(), true) + .put(Security.AUDIT_ENABLED_SETTING.getKey(), true) .build()); try { Security.validateAutoCreateIndex(Settings.builder() .put("action.auto_create_index", ".security") - .put(AuditTrailModule.ENABLED_SETTING.getKey(), true) - .put(AuditTrailModule.OUTPUTS_SETTING.getKey(), randomFrom("index", "logfile,index")) + .put(Security.AUDIT_ENABLED_SETTING.getKey(), true) + .put(Security.AUDIT_OUTPUTS_SETTING.getKey(), randomFrom("index", "logfile,index")) .build()); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException e) { @@ -176,8 +174,8 @@ public class SecuritySettingsTests extends ESTestCase { Security.validateAutoCreateIndex(Settings.builder() .put("action.auto_create_index", ".security_audit_log*,.security") - .put(AuditTrailModule.ENABLED_SETTING.getKey(), true) - .put(AuditTrailModule.OUTPUTS_SETTING.getKey(), randomFrom("index", "logfile,index")) + .put(Security.AUDIT_ENABLED_SETTING.getKey(), true) + .put(Security.AUDIT_OUTPUTS_SETTING.getKey(), randomFrom("index", "logfile,index")) .build()); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java new file mode 100644 index 00000000000..5d3b909d64b --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.extensions.XPackExtension; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail; +import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; +import org.elasticsearch.xpack.security.authc.Realm; +import org.elasticsearch.xpack.security.authc.Realms; +import org.elasticsearch.xpack.security.authc.file.FileRealm; + +import static org.mockito.Mockito.mock; + +public class SecurityTests extends ESTestCase { + + public static class DummyExtension extends XPackExtension { + private String realmType; + DummyExtension(String realmType) { + this.realmType = realmType; + } + @Override + public String name() { + return "dummy"; + } + @Override + public String description() { + return "dummy"; + } + @Override + public Map getRealms() { + return Collections.singletonMap(realmType, config -> null); + } + } + + private Collection createComponents(Settings testSettings, XPackExtension... extensions) throws IOException { + Settings settings = Settings.builder().put(testSettings) + .put("path.home", createTempDir()).build(); + Environment env = new Environment(settings); + Security security = new Security(settings, env, new XPackLicenseState()); + ThreadPool threadPool = mock(ThreadPool.class); + ClusterService clusterService = mock(ClusterService.class); + return security.createComponents(null, threadPool, clusterService, null, Arrays.asList(extensions)); + } + + private T findComponent(Class type, Collection components) { + for (Object obj : components) { + if (type.isInstance(obj)) { + return type.cast(obj); + } + } + return null; + } + + public void testCustomRealmExtension() throws Exception { + Collection components = createComponents(Settings.EMPTY, new DummyExtension("myrealm")); + Realms realms = findComponent(Realms.class, components); + assertNotNull(realms); + assertNotNull(realms.realmFactory("myrealm")); + } + + public void testCustomRealmExtensionConflict() throws Exception { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, + () -> createComponents(Settings.EMPTY, new DummyExtension(FileRealm.TYPE))); + assertEquals("Realm type [" + FileRealm.TYPE + "] is already registered", e.getMessage()); + } + + + public void testAuditEnabled() throws Exception { + Settings settings = Settings.builder().put(Security.AUDIT_ENABLED_SETTING.getKey(), true).build(); + Collection components = createComponents(settings); + AuditTrailService service = findComponent(AuditTrailService.class, components); + assertNotNull(service); + assertEquals(1, service.getAuditTrails().size()); + assertEquals(LoggingAuditTrail.NAME, service.getAuditTrails().get(0).name()); + } + + public void testDisabledByDefault() throws Exception { + Collection components = createComponents(Settings.EMPTY); + AuditTrailService auditTrailService = findComponent(AuditTrailService.class, components); + assertEquals(0, auditTrailService.getAuditTrails().size()); + } + + public void testIndexAuditTrail() throws Exception { + Settings settings = Settings.builder() + .put(Security.AUDIT_ENABLED_SETTING.getKey(), true) + .put(Security.AUDIT_OUTPUTS_SETTING.getKey(), "index").build(); + Collection components = createComponents(settings); + AuditTrailService service = findComponent(AuditTrailService.class, components); + assertNotNull(service); + assertEquals(1, service.getAuditTrails().size()); + assertEquals(IndexAuditTrail.NAME, service.getAuditTrails().get(0).name()); + } + + public void testIndexAndLoggingAuditTrail() throws Exception { + Settings settings = Settings.builder() + .put(Security.AUDIT_ENABLED_SETTING.getKey(), true) + .put(Security.AUDIT_OUTPUTS_SETTING.getKey(), "index,logfile").build(); + Collection components = createComponents(settings); + AuditTrailService service = findComponent(AuditTrailService.class, components); + assertNotNull(service); + assertEquals(2, service.getAuditTrails().size()); + assertEquals(IndexAuditTrail.NAME, service.getAuditTrails().get(0).name()); + assertEquals(LoggingAuditTrail.NAME, service.getAuditTrails().get(1).name()); + } + + public void testUnknownOutput() throws Exception { + Settings settings = Settings.builder() + .put(Security.AUDIT_ENABLED_SETTING.getKey(), true) + .put(Security.AUDIT_OUTPUTS_SETTING.getKey(), "foo").build(); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> createComponents(settings)); + assertEquals("Unknown audit trail output [foo]", e.getMessage()); + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/VersionCompatibilityTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/VersionCompatibilityTests.java index e262b646a98..d378361da4a 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/VersionCompatibilityTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/VersionCompatibilityTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.security; import org.elasticsearch.Version; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import static org.hamcrest.CoreMatchers.is; @@ -27,7 +28,7 @@ import static org.hamcrest.CoreMatchers.is; public class VersionCompatibilityTests extends ESTestCase { public void testCompatibility() { /** - * see https://github.com/elasticsearch/elasticsearch/issues/9372 {@link SecurityLicensee} + * see https://github.com/elasticsearch/elasticsearch/issues/9372 {@link XPackLicenseState} * Once es core supports merging cluster level custom metadata (licenses in our case), the tribe node will see some license * coming from the tribe and everything will be ok. * diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index 8794175d772..6019d77f682 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -5,6 +5,8 @@ */ package org.elasticsearch.xpack.security.action.filter; +import java.util.HashSet; + import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; @@ -12,24 +14,22 @@ import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.xpack.security.SecurityContext; -import org.elasticsearch.xpack.security.action.SecurityActionMapper; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; -import org.elasticsearch.xpack.security.user.SystemUser; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.authc.AuthenticationService; -import org.elasticsearch.xpack.security.authz.AuthorizationService; -import org.elasticsearch.xpack.security.crypto.CryptoService; -import org.elasticsearch.xpack.security.SecurityLicenseState; import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.security.SecurityContext; +import org.elasticsearch.xpack.security.action.SecurityActionMapper; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.authc.Authentication; +import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; +import org.elasticsearch.xpack.security.authc.AuthenticationService; +import org.elasticsearch.xpack.security.authz.AuthorizationService; +import org.elasticsearch.xpack.security.user.SystemUser; +import org.elasticsearch.xpack.security.user.User; +import org.elasticsearch.xpack.security.crypto.CryptoService; +import org.elasticsearch.license.XPackLicenseState; import org.junit.Before; -import java.util.HashSet; - import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; @@ -49,8 +49,8 @@ public class SecurityActionFilterTests extends ESTestCase { private AuthenticationService authcService; private AuthorizationService authzService; private CryptoService cryptoService; - private AuditTrail auditTrail; - private SecurityLicenseState securityLicenseState; + private AuditTrailService auditTrail; + private XPackLicenseState licenseState; private SecurityActionFilter filter; @Before @@ -58,13 +58,13 @@ public class SecurityActionFilterTests extends ESTestCase { authcService = mock(AuthenticationService.class); authzService = mock(AuthorizationService.class); cryptoService = mock(CryptoService.class); - auditTrail = mock(AuditTrail.class); - securityLicenseState = mock(SecurityLicenseState.class); - when(securityLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true); - when(securityLicenseState.statsAndHealthEnabled()).thenReturn(true); + auditTrail = mock(AuditTrailService.class); + licenseState = mock(XPackLicenseState.class); + when(licenseState.isAuthAllowed()).thenReturn(true); + when(licenseState.isStatsAndHealthAllowed()).thenReturn(true); ThreadPool threadPool = mock(ThreadPool.class); when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY)); - filter = new SecurityActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, securityLicenseState, + filter = new SecurityActionFilter(Settings.EMPTY, authcService, authzService, cryptoService, auditTrail, licenseState, new SecurityActionMapper(), new HashSet<>(), threadPool, mock(SecurityContext.class)); } @@ -135,7 +135,7 @@ public class SecurityActionFilterTests extends ESTestCase { ActionListener listener = mock(ActionListener.class); ActionFilterChain chain = mock(ActionFilterChain.class); Task task = mock(Task.class); - when(securityLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(false); + when(licenseState.isAuthAllowed()).thenReturn(false); filter.apply(task, "_action", request, listener, chain); verifyZeroInteractions(authcService); verifyZeroInteractions(authzService); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailModuleTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailModuleTests.java deleted file mode 100644 index 660304b31b7..00000000000 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailModuleTests.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.audit; - -import org.elasticsearch.common.inject.Guice; -import org.elasticsearch.common.inject.Injector; -import org.elasticsearch.common.io.stream.NamedWriteableRegistry; -import org.elasticsearch.common.network.NetworkModule; -import org.elasticsearch.common.network.NetworkService; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.settings.SettingsModule; -import org.elasticsearch.indices.breaker.CircuitBreakerService; -import org.elasticsearch.node.Node; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.threadpool.TestThreadPool; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.Transport; -import org.elasticsearch.transport.local.LocalTransport; -import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; - -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - -public class AuditTrailModuleTests extends ESTestCase { - public void testEnabled() throws Exception { - Settings settings = Settings.builder() - .put("client.type", "node") - .put(AuditTrailModule.ENABLED_SETTING.getKey(), false) - .build(); - SettingsModule settingsModule = new SettingsModule(settings, AuditTrailModule.ENABLED_SETTING); - Injector injector = Guice.createInjector(settingsModule, new AuditTrailModule(settings)); - AuditTrail auditTrail = injector.getInstance(AuditTrail.class); - assertThat(auditTrail, is(AuditTrail.NOOP)); - } - - public void testDisabledByDefault() throws Exception { - Settings settings = Settings.builder() - .put("client.type", "node").build(); - Injector injector = Guice.createInjector(new SettingsModule(settings), new AuditTrailModule(settings)); - AuditTrail auditTrail = injector.getInstance(AuditTrail.class); - assertThat(auditTrail, is(AuditTrail.NOOP)); - } - - public void testLogfile() throws Exception { - Settings settings = Settings.builder() - .put(AuditTrailModule.ENABLED_SETTING.getKey(), true) - .put("client.type", "node") - .build(); - ThreadPool pool = new TestThreadPool("testLogFile"); - try { - SettingsModule settingsModule = new SettingsModule(settings, AuditTrailModule.ENABLED_SETTING); - Injector injector = Guice.createInjector( - settingsModule, - new NetworkModule(new NetworkService(settings), settings, false, new NamedWriteableRegistry()) { - @Override - protected void configure() { - bind(Transport.class).to(LocalTransport.class).asEagerSingleton(); - } - }, - new AuditTrailModule(settings), - b -> { - b.bind(CircuitBreakerService.class).toInstance(Node.createCircuitBreakerService(settingsModule.getSettings(), - settingsModule.getClusterSettings())); - b.bind(ThreadPool.class).toInstance(pool); - } - ); - AuditTrail auditTrail = injector.getInstance(AuditTrail.class); - assertThat(auditTrail, instanceOf(AuditTrailService.class)); - AuditTrailService service = (AuditTrailService) auditTrail; - assertThat(service.auditTrails, notNullValue()); - assertThat(service.auditTrails.length, is(1)); - assertThat(service.auditTrails[0], instanceOf(LoggingAuditTrail.class)); - } finally { - pool.shutdown(); - } - } - - public void testUnknownOutput() throws Exception { - Settings settings = Settings.builder() - .put(AuditTrailModule.ENABLED_SETTING.getKey(), true) - .put(AuditTrailModule.OUTPUTS_SETTING.getKey() , "foo") - .put("client.type", "node") - .build(); - SettingsModule settingsModule = new SettingsModule(settings, AuditTrailModule.ENABLED_SETTING, AuditTrailModule.OUTPUTS_SETTING); - try { - Guice.createInjector(settingsModule, new AuditTrailModule(settings)); - fail("Expect initialization to fail when an unknown audit trail output is configured"); - } catch (Exception e) { - // expected - } - } -} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailServiceTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailServiceTests.java index cfcd031d0a4..3b71f34cd58 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailServiceTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/AuditTrailServiceTests.java @@ -7,7 +7,7 @@ package org.elasticsearch.xpack.security.audit; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.xpack.security.SecurityLicenseState; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.AuthenticationToken; import org.elasticsearch.xpack.security.transport.filter.IPFilter; @@ -17,10 +17,10 @@ import org.elasticsearch.transport.TransportMessage; import org.junit.Before; import java.net.InetAddress; -import java.util.HashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; -import static java.util.Collections.unmodifiableSet; +import static java.util.Collections.unmodifiableList; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -30,26 +30,26 @@ import static org.mockito.Mockito.when; * */ public class AuditTrailServiceTests extends ESTestCase { - private Set auditTrails; + private List auditTrails; private AuditTrailService service; private AuthenticationToken token; private TransportMessage message; private RestRequest restRequest; - private SecurityLicenseState securityLicenseState; - private boolean auditingEnabled; + private XPackLicenseState licenseState; + private boolean isAuditingAllowed; @Before public void init() throws Exception { - Set auditTrailsBuilder = new HashSet<>(); + List auditTrailsBuilder = new ArrayList<>(); for (int i = 0; i < randomIntBetween(1, 4); i++) { auditTrailsBuilder.add(mock(AuditTrail.class)); } - auditTrails = unmodifiableSet(auditTrailsBuilder); - securityLicenseState = mock(SecurityLicenseState.class); - service = new AuditTrailService(Settings.EMPTY, auditTrails, securityLicenseState); - auditingEnabled = randomBoolean(); - when(securityLicenseState.auditingEnabled()).thenReturn(auditingEnabled); + auditTrails = unmodifiableList(auditTrailsBuilder); + licenseState = mock(XPackLicenseState.class); + service = new AuditTrailService(Settings.EMPTY, auditTrails, licenseState); + isAuditingAllowed = randomBoolean(); + when(licenseState.isAuditingAllowed()).thenReturn(isAuditingAllowed); token = mock(AuthenticationToken.class); message = mock(TransportMessage.class); restRequest = mock(RestRequest.class); @@ -57,8 +57,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAuthenticationFailed() throws Exception { service.authenticationFailed(token, "_action", message); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).authenticationFailed(token, "_action", message); } @@ -69,8 +69,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAuthenticationFailedNoToken() throws Exception { service.authenticationFailed("_action", message); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).authenticationFailed("_action", message); } @@ -81,8 +81,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAuthenticationFailedRestNoToken() throws Exception { service.authenticationFailed(restRequest); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).authenticationFailed(restRequest); } @@ -93,8 +93,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAuthenticationFailedRest() throws Exception { service.authenticationFailed(token, restRequest); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).authenticationFailed(token, restRequest); } @@ -105,8 +105,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAuthenticationFailedRealm() throws Exception { service.authenticationFailed("_realm", token, "_action", message); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).authenticationFailed("_realm", token, "_action", message); } @@ -117,8 +117,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAuthenticationFailedRestRealm() throws Exception { service.authenticationFailed("_realm", token, restRequest); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).authenticationFailed("_realm", token, restRequest); } @@ -129,8 +129,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAnonymousAccess() throws Exception { service.anonymousAccessDenied("_action", message); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).anonymousAccessDenied("_action", message); } @@ -142,8 +142,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAccessGranted() throws Exception { User user = new User("_username", "r1"); service.accessGranted(user, "_action", message); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).accessGranted(user, "_action", message); } @@ -155,8 +155,8 @@ public class AuditTrailServiceTests extends ESTestCase { public void testAccessDenied() throws Exception { User user = new User("_username", "r1"); service.accessDenied(user, "_action", message); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).accessDenied(user, "_action", message); } @@ -169,8 +169,8 @@ public class AuditTrailServiceTests extends ESTestCase { InetAddress inetAddress = InetAddress.getLoopbackAddress(); SecurityIpFilterRule rule = randomBoolean() ? SecurityIpFilterRule.ACCEPT_ALL : IPFilter.DEFAULT_PROFILE_ACCEPT_ALL; service.connectionGranted(inetAddress, "client", rule); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).connectionGranted(inetAddress, "client", rule); } @@ -183,8 +183,8 @@ public class AuditTrailServiceTests extends ESTestCase { InetAddress inetAddress = InetAddress.getLoopbackAddress(); SecurityIpFilterRule rule = new SecurityIpFilterRule(false, "_all"); service.connectionDenied(inetAddress, "client", rule); - verify(securityLicenseState).auditingEnabled(); - if (auditingEnabled) { + verify(licenseState).isAuditingAllowed(); + if (isAuditingAllowed) { for (AuditTrail auditTrail : auditTrails) { verify(auditTrail).connectionDenied(inetAddress, "client", rule); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailMutedTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailMutedTests.java index 587ce8141ac..a228791c25a 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailMutedTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailMutedTests.java @@ -5,25 +5,27 @@ */ package org.elasticsearch.xpack.security.audit.index; +import java.net.InetAddress; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + import org.elasticsearch.action.Action; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.client.Client; -import org.elasticsearch.client.FilterClient; import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress; -import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.Transport; +import org.elasticsearch.transport.MockTransportClient; import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.audit.index.IndexAuditTrail.State; @@ -34,11 +36,6 @@ import org.elasticsearch.xpack.security.user.User; import org.junit.After; import org.junit.Before; -import java.net.InetAddress; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; - import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; @@ -49,7 +46,7 @@ public class IndexAuditTrailMutedTests extends ESTestCase { private InternalClient client; private TransportClient transportClient; private ThreadPool threadPool; - private Transport transport; + private ClusterService clusterService; private IndexAuditTrail auditTrail; private AtomicBoolean messageEnqueued; @@ -57,16 +54,17 @@ public class IndexAuditTrailMutedTests extends ESTestCase { @Before public void setup() { - transport = mock(Transport.class); - when(transport.boundAddress()).thenReturn(new BoundTransportAddress(new TransportAddress[] { LocalTransportAddress.buildUnique() }, - LocalTransportAddress.buildUnique())); + DiscoveryNode localNode = mock(DiscoveryNode.class); + when(localNode.getHostAddress()).thenReturn(LocalTransportAddress.buildUnique().toString()); + clusterService = mock(ClusterService.class); + when(clusterService.localNode()).thenReturn(localNode); threadPool = new TestThreadPool("index audit trail tests"); - transportClient = TransportClient.builder().settings(Settings.EMPTY).build(); + transportClient = new MockTransportClient(Settings.EMPTY); clientCalled = new AtomicBoolean(false); - class IClient extends FilterClient implements InternalClient { + class IClient extends InternalClient { IClient(Client transportClient){ - super(transportClient); + super(Settings.EMPTY, null, transportClient, null); } @Override protected , Response extends ActionResponse, RequestBuilder extends @@ -257,7 +255,7 @@ public class IndexAuditTrailMutedTests extends ESTestCase { IndexAuditTrail createAuditTrail(String[] excludes) { Settings settings = IndexAuditTrailTests.levelSettings(null, excludes); - auditTrail = new IndexAuditTrail(settings, transport, Providers.of(client), threadPool, mock(ClusterService.class)) { + auditTrail = new IndexAuditTrail(settings, client, threadPool, clusterService) { @Override void putTemplate(Settings settings) { // make this a no-op so we don't have to stub out unnecessary client activities diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java index 2144bc8ba06..820d3a1c090 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailTests.java @@ -5,6 +5,17 @@ */ package org.elasticsearch.xpack.security.audit.index; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + import org.apache.lucene.util.SetOnce; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; @@ -15,15 +26,15 @@ import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.Client; import org.elasticsearch.client.Requests; import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Priority; -import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.network.NetworkAddress; +import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress; -import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.search.SearchHit; import org.elasticsearch.test.ESIntegTestCase; @@ -32,7 +43,7 @@ import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.Transport; +import org.elasticsearch.transport.MockTcpTransportPlugin; import org.elasticsearch.transport.TransportInfo; import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.transport.TransportRequest; @@ -42,7 +53,7 @@ import org.elasticsearch.xpack.security.authc.AuthenticationToken; import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; import org.joda.time.DateTime; @@ -53,16 +64,6 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - import static org.elasticsearch.test.ESIntegTestCase.Scope.SUITE; import static org.elasticsearch.test.InternalTestCluster.clusterName; import static org.elasticsearch.xpack.security.audit.index.IndexNameResolver.Rollover.DAILY; @@ -140,11 +141,20 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { Settings.Builder builder = Settings.builder() .put(super.nodeSettings(nodeOrdinal)) .put(Security.enabledSetting(), useSecurity); + if (useSecurity == false && builder.get(NetworkModule.TRANSPORT_TYPE_KEY) == null) { + builder.put(NetworkModule.TRANSPORT_TYPE_KEY, MockTcpTransportPlugin.MOCK_TCP_TRANSPORT_NAME); + } return builder.build(); } }; - remoteCluster = new InternalTestCluster("network", randomLong(), createTempDir(), false, numNodes, numNodes, cluster2Name, - cluster2SettingsSource, 0, false, SECOND_CLUSTER_NODE_PREFIX, getMockPlugins(), + + + Set> mockPlugins = new HashSet<>(getMockPlugins()); + if (useSecurity == false) { + mockPlugins.add(MockTcpTransportPlugin.class); + } + remoteCluster = new InternalTestCluster(randomLong(), createTempDir(), false, numNodes, numNodes, cluster2Name, + cluster2SettingsSource, 0, false, SECOND_CLUSTER_NODE_PREFIX, mockPlugins, useSecurity ? getClientWrapper() : Function.identity()); remoteCluster.beforeTest(random(), 0.5); @@ -163,7 +173,7 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { builder.put("xpack.security.audit.index.client." + entry.getKey(), entry.getValue()); } } else { - builder.put("xpack.security.audit.index.client." + SecurityNettyTransport.SSL_SETTING.getKey(), false); + builder.put("xpack.security.audit.index.client." + SecurityNetty3Transport.SSL_SETTING.getKey(), false); } remoteSettings = builder.build(); } @@ -253,13 +263,14 @@ public class IndexAuditTrailTests extends SecurityIntegTestCase { Settings settings = builder.put(settings(rollover, includes, excludes)).build(); logger.info("--> settings: [{}]", settings.getAsMap().toString()); - Transport transport = mock(Transport.class); - BoundTransportAddress boundTransportAddress = new BoundTransportAddress(new TransportAddress[]{ remoteHostAddress()}, - remoteHostAddress()); - when(transport.boundAddress()).thenReturn(boundTransportAddress); + DiscoveryNode localNode = mock(DiscoveryNode.class); + when(localNode.getHostAddress()).thenReturn(remoteHostAddress().getAddress()); + when(localNode.getHostName()).thenReturn(remoteHostAddress().getHost()); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.localNode()).thenReturn(localNode); threadPool = new TestThreadPool("index audit trail tests"); enqueuedMessage = new SetOnce<>(); - auditor = new IndexAuditTrail(settings, transport, Providers.of(internalClient()), threadPool, mock(ClusterService.class)) { + auditor = new IndexAuditTrail(settings, internalClient(), threadPool, clusterService) { @Override void enqueue(Message message, String type) { enqueuedMessage.set(message); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailUpdateMappingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailUpdateMappingTests.java index b6e4c6cfb51..77d62a66e28 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailUpdateMappingTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/IndexAuditTrailUpdateMappingTests.java @@ -5,23 +5,20 @@ */ package org.elasticsearch.xpack.security.audit.index; +import java.util.Locale; + import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.inject.util.Providers; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.LocalTransportAddress; -import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.Transport; import org.junit.After; import org.junit.Before; -import java.util.Locale; - import static org.elasticsearch.xpack.security.audit.index.IndexNameResolver.Rollover.DAILY; import static org.elasticsearch.xpack.security.audit.index.IndexNameResolver.Rollover.HOURLY; import static org.elasticsearch.xpack.security.audit.index.IndexNameResolver.Rollover.MONTHLY; @@ -48,11 +45,11 @@ public class IndexAuditTrailUpdateMappingTests extends SecurityIntegTestCase { IndexNameResolver.Rollover rollover = randomFrom(HOURLY, DAILY, WEEKLY, MONTHLY); Settings settings = Settings.builder().put("xpack.security.audit.index.rollover", rollover.name().toLowerCase(Locale.ENGLISH)) .put("path.home", createTempDir()).build(); - Transport transport = mock(Transport.class); - when(transport.boundAddress()).thenReturn(new BoundTransportAddress(new TransportAddress[] { LocalTransportAddress.buildUnique() }, - LocalTransportAddress.buildUnique())); - auditor = new IndexAuditTrail(settings, transport, Providers.of(internalClient()), threadPool, - mock(ClusterService.class)); + DiscoveryNode localNode = mock(DiscoveryNode.class); + when(localNode.getHostAddress()).thenReturn(LocalTransportAddress.buildUnique().toString()); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.localNode()).thenReturn(localNode); + auditor = new IndexAuditTrail(settings, internalClient(), threadPool, clusterService); // before starting we add an event auditor.authenticationFailed(new FakeRestRequest()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java index 477e828d149..d7eb76e1da1 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/index/RemoteIndexAuditTrailStartingTests.java @@ -14,6 +14,8 @@ import org.elasticsearch.test.ESIntegTestCase.Scope; import org.elasticsearch.test.InternalTestCluster; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; +import org.elasticsearch.xpack.security.audit.AuditTrail; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.junit.After; import org.junit.Before; @@ -22,6 +24,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -105,7 +108,7 @@ public class RemoteIndexAuditTrailStartingTests extends SecurityIntegTestCase { return builder.build(); } }; - remoteCluster = new InternalTestCluster("network", randomLong(), createTempDir(), false, + remoteCluster = new InternalTestCluster(randomLong(), createTempDir(), false, numNodes, numNodes, cluster2Name, cluster2SettingsSource, 0, false, SECOND_CLUSTER_NODE_PREFIX, getMockPlugins(), getClientWrapper()); remoteCluster.beforeTest(random(), 0.5); @@ -114,10 +117,10 @@ public class RemoteIndexAuditTrailStartingTests extends SecurityIntegTestCase { @After public void stopRemoteCluster() throws Exception { if (remoteCluster != null) { - Iterable auditTrails = internalCluster().getInstances(IndexAuditTrail.class); + /*Iterable auditTrails = internalCluster().getInstances(IndexAuditTrail.class); for (IndexAuditTrail auditTrail : auditTrails) { auditTrail.close(); - } + }*/ try { remoteCluster.wipe(Collections.emptySet()); @@ -128,19 +131,22 @@ public class RemoteIndexAuditTrailStartingTests extends SecurityIntegTestCase { } // stop the index audit trail so that the shards aren't locked causing the test to fail - if (outputs.contains("index")) { + /*if (outputs.contains("index")) { Iterable auditTrails = internalCluster().getInstances(IndexAuditTrail.class); for (IndexAuditTrail auditTrail : auditTrails) { auditTrail.close(); } - } + }*/ } public void testThatRemoteAuditInstancesAreStarted() throws Exception { - Iterable auditTrails = remoteCluster.getInstances(IndexAuditTrail.class); - for (final IndexAuditTrail auditTrail : auditTrails) { - awaitBusy(() -> auditTrail.state() == IndexAuditTrail.State.STARTED, 2L, TimeUnit.SECONDS); - assertThat(auditTrail.state(), is(IndexAuditTrail.State.STARTED)); - } + AuditTrailService auditTrailService = remoteCluster.getInstance(AuditTrailService.class); + Optional auditTrail = auditTrailService.getAuditTrails().stream() + .filter(t -> t.name().equals(IndexAuditTrail.NAME)).findFirst(); + assertTrue(auditTrail.isPresent()); + IndexAuditTrail indexAuditTrail = (IndexAuditTrail)auditTrail.get(); + + awaitBusy(() -> indexAuditTrail.state() == IndexAuditTrail.State.STARTED, 2L, TimeUnit.SECONDS); + assertThat(indexAuditTrail.state(), is(IndexAuditTrail.State.STARTED)); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java index 11104ff64d5..fc447fa2628 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.audit.logfile; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.component.Lifecycle; @@ -41,9 +43,6 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -/** - * - */ public class LoggingAuditTrailTests extends ESTestCase { private static enum RestContent { VALID() { @@ -102,7 +101,8 @@ public class LoggingAuditTrailTests extends ESTestCase { private String prefix; private Settings settings; - private Transport transport; + private DiscoveryNode localNode; + private ClusterService clusterService; private ThreadContext threadContext; @Before @@ -112,21 +112,20 @@ public class LoggingAuditTrailTests extends ESTestCase { .put("xpack.security.audit.logfile.prefix.emit_node_host_name", randomBoolean()) .put("xpack.security.audit.logfile.prefix.emit_node_name", randomBoolean()) .build(); - transport = mock(Transport.class); - when(transport.lifecycleState()).thenReturn(Lifecycle.State.STARTED); - when(transport.boundAddress()).thenReturn(new BoundTransportAddress(new TransportAddress[] { LocalTransportAddress.buildUnique() }, - LocalTransportAddress.buildUnique())); - prefix = LoggingAuditTrail.resolvePrefix(settings, transport); + localNode = mock(DiscoveryNode.class); + when(localNode.getHostAddress()).thenReturn(LocalTransportAddress.buildUnique().toString()); + clusterService = mock(ClusterService.class); + when(clusterService.localNode()).thenReturn(localNode); + prefix = LoggingAuditTrail.resolvePrefix(settings, localNode); } public void testAnonymousAccessDeniedTransport() throws Exception { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, clusterService.localNode(), threadContext); auditTrail.anonymousAccessDenied("_action", message); switch (level) { case ERROR: @@ -164,8 +163,7 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.anonymousAccessDenied(request); switch (level) { case ERROR: @@ -188,10 +186,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext);; + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext);; auditTrail.authenticationFailed(new MockToken(), "_action", message); switch (level) { case ERROR: @@ -222,10 +219,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext);; + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext);; auditTrail.authenticationFailed("_action", message); switch (level) { case ERROR: @@ -261,8 +257,7 @@ public class LoggingAuditTrailTests extends ESTestCase { when(request.uri()).thenReturn("_uri"); String expectedMessage = prepareRestContent(request); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed(new MockToken(), request); switch (level) { case ERROR: @@ -289,8 +284,7 @@ public class LoggingAuditTrailTests extends ESTestCase { when(request.uri()).thenReturn("_uri"); String expectedMessage = prepareRestContent(request); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed(request); switch (level) { case ERROR: @@ -311,10 +305,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext);; + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext);; auditTrail.authenticationFailed("_realm", new MockToken(), "_action", message); switch (level) { case ERROR: @@ -344,8 +337,7 @@ public class LoggingAuditTrailTests extends ESTestCase { when(request.uri()).thenReturn("_uri"); String expectedMessage = prepareRestContent(request); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed("_realm", new MockToken(), request); switch (level) { case ERROR: @@ -366,10 +358,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); boolean runAs = randomBoolean(); User user; if (runAs) { @@ -411,10 +402,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); auditTrail.accessGranted(SystemUser.INSTANCE, "internal:_action", message); switch (level) { case ERROR: @@ -440,10 +430,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); boolean runAs = randomBoolean(); User user; if (runAs) { @@ -485,10 +474,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); boolean runAs = randomBoolean(); User user; if (runAs) { @@ -534,8 +522,7 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.tamperedRequest(request); switch (level) { case ERROR: @@ -557,10 +544,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.tamperedRequest(action, message); switch (level) { case ERROR: @@ -599,10 +585,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.tamperedRequest(user, action, message); switch (level) { case ERROR: @@ -633,8 +618,7 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); InetAddress inetAddress = InetAddress.getLoopbackAddress(); SecurityIpFilterRule rule = new SecurityIpFilterRule(false, "_all"); auditTrail.connectionDenied(inetAddress, "default", rule); @@ -656,8 +640,7 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); InetAddress inetAddress = InetAddress.getLoopbackAddress(); SecurityIpFilterRule rule = IPFilter.DEFAULT_PROFILE_ACCEPT_ALL; auditTrail.connectionGranted(inetAddress, "default", rule); @@ -680,10 +663,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = new MockMessage(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); User user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); auditTrail.runAsGranted(user, "_action", message); switch (level) { @@ -707,10 +689,9 @@ public class LoggingAuditTrailTests extends ESTestCase { for (Level level : Level.values()) { threadContext = new ThreadContext(Settings.EMPTY); CapturingLogger logger = new CapturingLogger(level); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, transport, logger, threadContext); - auditTrail.start(); + LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); TransportMessage message = new MockMessage(threadContext); - String origins = LoggingAuditTrail.originAttributes(message, transport, threadContext); + String origins = LoggingAuditTrail.originAttributes(message, localNode, threadContext); User user = new User("_username", new String[]{"r1"}, new User("running as", new String[] {"r2"})); auditTrail.runAsDenied(user, "_action", message); switch (level) { @@ -733,7 +714,7 @@ public class LoggingAuditTrailTests extends ESTestCase { public void testOriginAttributes() throws Exception { threadContext = new ThreadContext(Settings.EMPTY); MockMessage message = new MockMessage(threadContext); - String text = LoggingAuditTrail.originAttributes(message, transport, threadContext);; + String text = LoggingAuditTrail.originAttributes(message, localNode, threadContext);; InetSocketAddress restAddress = RemoteHostHeader.restRemoteAddress(threadContext); if (restAddress != null) { assertThat(text, equalTo("origin_type=[rest], origin_address=[" + @@ -742,8 +723,7 @@ public class LoggingAuditTrailTests extends ESTestCase { } TransportAddress address = message.remoteAddress(); if (address == null) { - assertThat(text, equalTo("origin_type=[local_node], origin_address=[" + - transport.boundAddress().publishAddress().getAddress() + "]")); + assertThat(text, equalTo("origin_type=[local_node], origin_address=[" + localNode.getHostAddress() + "]")); return; } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationModuleTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationModuleTests.java deleted file mode 100644 index aedd3a25917..00000000000 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationModuleTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.authc; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectoryRealm; -import org.elasticsearch.xpack.security.authc.file.FileRealm; -import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; -import org.elasticsearch.xpack.security.authc.pki.PkiRealm; -import org.elasticsearch.test.ESTestCase; - -import static org.hamcrest.Matchers.containsString; - -/** - * Unit tests for the AuthenticationModule - */ -public class AuthenticationModuleTests extends ESTestCase { - public void testAddingReservedRealmType() { - Settings settings = Settings.EMPTY; - AuthenticationModule module = new AuthenticationModule(settings); - try { - module.addCustomRealm(randomFrom(PkiRealm.TYPE, LdapRealm.TYPE, ActiveDirectoryRealm.TYPE, FileRealm.TYPE), - randomFrom(PkiRealm.Factory.class, LdapRealm.Factory.class, ActiveDirectoryRealm.Factory.class, - FileRealm.Factory.class)); - fail("overriding a built in realm type is not allowed!"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("cannot redefine")); - } - } - - public void testAddingNullOrEmptyType() { - Settings settings = Settings.EMPTY; - AuthenticationModule module = new AuthenticationModule(settings); - try { - module.addCustomRealm(randomBoolean() ? null : "", - randomFrom(PkiRealm.Factory.class, LdapRealm.Factory.class, ActiveDirectoryRealm.Factory.class, - FileRealm.Factory.class)); - fail("type must not be null"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("null or empty")); - } - } - - public void testAddingNullFactory() { - Settings settings = Settings.EMPTY; - AuthenticationModule module = new AuthenticationModule(settings); - try { - module.addCustomRealm(randomAsciiOfLength(7), null); - fail("factory must not be null"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("null")); - } - } -} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/InternalAuthenticationServiceTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java similarity index 91% rename from elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/InternalAuthenticationServiceTests.java rename to elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index f76d99ff4c4..64161fc7607 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/InternalAuthenticationServiceTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -5,6 +5,10 @@ */ package org.elasticsearch.xpack.security.authc; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; + import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.io.stream.BytesStreamOutput; @@ -12,33 +16,27 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; -import org.elasticsearch.rest.RestController; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; -import org.elasticsearch.xpack.security.authc.InternalAuthenticationService.Authenticator; -import org.elasticsearch.xpack.security.SecurityLicenseState.EnabledRealmType; -import org.elasticsearch.xpack.security.user.AnonymousUser; -import org.elasticsearch.xpack.security.user.SystemUser; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; -import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import org.elasticsearch.xpack.security.crypto.CryptoService; -import org.elasticsearch.xpack.security.SecurityLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportMessage; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; +import org.elasticsearch.xpack.security.authc.AuthenticationService.Authenticator; +import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.authc.support.SecuredString; +import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.security.crypto.CryptoService; +import org.elasticsearch.xpack.security.user.AnonymousUser; +import org.elasticsearch.xpack.security.user.SystemUser; +import org.elasticsearch.xpack.security.user.User; import org.junit.After; import org.junit.Before; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; - -import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; +import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -60,20 +58,19 @@ import static org.mockito.Mockito.when; /** * */ -public class InternalAuthenticationServiceTests extends ESTestCase { +public class AuthenticationServiceTests extends ESTestCase { - InternalAuthenticationService service; + AuthenticationService service; TransportMessage message; RestRequest restRequest; Realms realms; Realm firstRealm; Realm secondRealm; - AuditTrail auditTrail; + AuditTrailService auditTrail; AuthenticationToken token; CryptoService cryptoService; ThreadPool threadPool; ThreadContext threadContext; - RestController controller; @Before public void init() throws Exception { @@ -90,11 +87,11 @@ public class InternalAuthenticationServiceTests extends ESTestCase { .put("path.home", createTempDir()) .put("node.name", "authc_test") .build(); - SecurityLicenseState securityLicenseState = mock(SecurityLicenseState.class); - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL); - when(securityLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true); - realms = new Realms(Settings.EMPTY, new Environment(settings), Collections.emptyMap(), securityLicenseState, - mock(ReservedRealm.class)) { + XPackLicenseState licenseState = mock(XPackLicenseState.class); + when(licenseState.allowedRealmType()).thenReturn(XPackLicenseState.AllowedRealmType.ALL); + when(licenseState.isAuthAllowed()).thenReturn(true); + realms = new Realms(Settings.EMPTY, new Environment(settings), Collections.emptyMap(), + licenseState, mock(ReservedRealm.class)) { @Override protected void doStart() { @@ -106,14 +103,13 @@ public class InternalAuthenticationServiceTests extends ESTestCase { realms.start(); cryptoService = mock(CryptoService.class); - auditTrail = mock(AuditTrail.class); + auditTrail = mock(AuditTrailService.class); threadPool = mock(ThreadPool.class); threadContext = new ThreadContext(Settings.EMPTY); - controller = mock(RestController.class); when(threadPool.getThreadContext()).thenReturn(threadContext); when(cryptoService.sign(any(String.class))).thenReturn("_signed_auth"); - service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(settings, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); } @After @@ -311,8 +307,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { InternalMessage message1 = new InternalMessage(); ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY); when(threadPool.getThreadContext()).thenReturn(threadContext1); - service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY)); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY)); @@ -325,8 +321,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { // checking authentication from the user header threadContext1 = new ThreadContext(Settings.EMPTY); when(threadPool.getThreadContext()).thenReturn(threadContext1); - service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY)); when(cryptoService.unsignAndVerify("_signed_auth")).thenReturn(authentication.encode()); @@ -337,8 +333,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { threadContext1.readHeaders(input); when(threadPool.getThreadContext()).thenReturn(threadContext1); - service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE); assertThat(result, notNullValue()); assertThat(result.getUser(), equalTo(user1)); @@ -346,9 +342,9 @@ public class InternalAuthenticationServiceTests extends ESTestCase { } public void testAuthenticateTransportContextAndHeaderNoSigning() throws Exception { - Settings settings = Settings.builder().put(InternalAuthenticationService.SIGN_USER_HEADER.getKey(), false).build(); - service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + Settings settings = Settings.builder().put(AuthenticationService.SIGN_USER_HEADER.getKey(), false).build(); + service = new AuthenticationService(settings, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); User user1 = new User("username", "r1", "r2"); when(firstRealm.supports(token)).thenReturn(true); @@ -364,8 +360,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { InternalMessage message1 = new InternalMessage(); ThreadContext threadContext1 = new ThreadContext(Settings.EMPTY); when(threadPool.getThreadContext()).thenReturn(threadContext1); - service = new InternalAuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(Settings.EMPTY, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); threadContext1.putTransient(Authentication.AUTHENTICATION_KEY, threadContext.getTransient(Authentication.AUTHENTICATION_KEY)); threadContext1.putHeader(Authentication.AUTHENTICATION_KEY, threadContext.getHeader(Authentication.AUTHENTICATION_KEY)); Authentication ctxAuth = service.authenticate("_action", message1, SystemUser.INSTANCE); @@ -384,8 +380,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { threadContext1.readHeaders(input); when(threadPool.getThreadContext()).thenReturn(threadContext1); - service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(settings, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); Authentication result = service.authenticate("_action", new InternalMessage(), SystemUser.INSTANCE); assertThat(result, notNullValue()); assertThat(result.getUser(), equalTo(user1)); @@ -447,8 +443,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { } Settings settings = builder.build(); AnonymousUser.initialize(settings); - service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(), - threadPool, controller); + service = new AuthenticationService(settings, realms, auditTrail, cryptoService, new DefaultAuthenticationFailureHandler(), + threadPool); RestRequest request = new FakeRestRequest(); Authentication result = service.authenticate(request); @@ -463,8 +459,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { .putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3") .build(); AnonymousUser.initialize(settings); - service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(settings, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); InternalMessage message = new InternalMessage(); Authentication result = service.authenticate("_action", message, null); @@ -478,8 +474,8 @@ public class InternalAuthenticationServiceTests extends ESTestCase { .putArray(AnonymousUser.ROLES_SETTING.getKey(), "r1", "r2", "r3") .build(); AnonymousUser.initialize(settings); - service = new InternalAuthenticationService(settings, realms, auditTrail, cryptoService, - new DefaultAuthenticationFailureHandler(), threadPool, controller); + service = new AuthenticationService(settings, realms, auditTrail, cryptoService, + new DefaultAuthenticationFailureHandler(), threadPool); InternalMessage message = new InternalMessage(); @@ -567,7 +563,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase { public void testRealmLookupThrowingException() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); - threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"); + threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as"); when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"})); @@ -585,7 +581,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase { public void testRealmLookupThrowingExceptionRest() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); - threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"); + threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as"); when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"})); @@ -603,7 +599,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase { public void testRunAsLookupSameRealm() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); - threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"); + threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as"); when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"})); @@ -630,7 +626,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase { public void testRunAsLookupDifferentRealm() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); - threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "run_as"); + threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, "run_as"); when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.authenticate(token)).thenReturn(new User("lookup user", new String[]{"user"})); @@ -659,7 +655,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase { public void testRunAsWithEmptyRunAsUsernameRest() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); User user = new User("lookup user", new String[]{"user"}); - threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ""); + threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, ""); when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.authenticate(token)).thenReturn(user); @@ -677,7 +673,7 @@ public class InternalAuthenticationServiceTests extends ESTestCase { public void testRunAsWithEmptyRunAsUsername() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); User user = new User("lookup user", new String[]{"user"}); - threadContext.putHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, ""); + threadContext.putHeader(AuthenticationService.RUN_AS_USER_HEADER, ""); when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); when(secondRealm.authenticate(token)).thenReturn(user); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java index f58558aec77..eebc8b139b7 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java @@ -8,13 +8,13 @@ package org.elasticsearch.xpack.security.authc; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; -import org.elasticsearch.xpack.security.SecurityLicenseState.EnabledRealmType; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; import org.elasticsearch.xpack.security.authc.file.FileRealm; import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; -import org.elasticsearch.xpack.security.SecurityLicenseState; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.license.XPackLicenseState.AllowedRealmType; import org.elasticsearch.test.ESTestCase; import org.junit.Before; @@ -33,27 +33,24 @@ import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -/** - * - */ public class RealmsTests extends ESTestCase { private Map factories; - private SecurityLicenseState securityLicenseState; + private XPackLicenseState licenseState; private ReservedRealm reservedRealm; @Before public void init() throws Exception { factories = new HashMap<>(); - factories.put(FileRealm.TYPE, new DummyRealm.Factory(FileRealm.TYPE, true)); - factories.put(NativeRealm.TYPE, new DummyRealm.Factory(NativeRealm.TYPE, true)); + factories.put(FileRealm.TYPE, config -> new DummyRealm(FileRealm.TYPE, config)); + factories.put(NativeRealm.TYPE, config -> new DummyRealm(NativeRealm.TYPE, config)); for (int i = 0; i < randomIntBetween(1, 5); i++) { - DummyRealm.Factory factory = new DummyRealm.Factory("type_" + i, rarely()); - factories.put("type_" + i, factory); + String name = "type_" + i; + factories.put(name, config -> new DummyRealm(name, config)); } - securityLicenseState = mock(SecurityLicenseState.class); + licenseState = mock(XPackLicenseState.class); reservedRealm = mock(ReservedRealm.class); - when(securityLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(true); - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.ALL); + when(licenseState.isAuthAllowed()).thenReturn(true); + when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.ALL); } public void testWithSettings() throws Exception { @@ -72,7 +69,7 @@ public class RealmsTests extends ESTestCase { } Settings settings = builder.build(); Environment env = new Environment(settings); - Realms realms = new Realms(settings, env, factories, securityLicenseState, reservedRealm); + Realms realms = new Realms(settings, env, factories, licenseState, reservedRealm); realms.start(); Iterator iterator = realms.iterator(); @@ -101,7 +98,7 @@ public class RealmsTests extends ESTestCase { .build(); Environment env = new Environment(settings); try { - new Realms(settings, env, factories, securityLicenseState, reservedRealm).start(); + new Realms(settings, env, factories, licenseState, reservedRealm).start(); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), containsString("multiple [file] realms are configured")); @@ -110,7 +107,7 @@ public class RealmsTests extends ESTestCase { public void testWithEmptySettings() throws Exception { Realms realms = new Realms(Settings.EMPTY, new Environment(Settings.builder().put("path.home", createTempDir()).build()), - factories, securityLicenseState, reservedRealm); + factories, licenseState, reservedRealm); realms.start(); Iterator iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); @@ -143,7 +140,7 @@ public class RealmsTests extends ESTestCase { } Settings settings = builder.build(); Environment env = new Environment(settings); - Realms realms = new Realms(settings, env, factories, securityLicenseState, reservedRealm); + Realms realms = new Realms(settings, env, factories, licenseState, reservedRealm); realms.start(); // this is the iterator when licensed @@ -161,7 +158,7 @@ public class RealmsTests extends ESTestCase { i++; } - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.DEFAULT); + when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.DEFAULT); iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); @@ -177,7 +174,7 @@ public class RealmsTests extends ESTestCase { assertThat(realm.name(), equalTo("default_" + NativeRealm.TYPE)); assertThat(iter.hasNext(), is(false)); - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.NATIVE); + when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE); iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); @@ -195,7 +192,7 @@ public class RealmsTests extends ESTestCase { } public void testUnlicensedWithInternalRealms() throws Exception { - factories.put(LdapRealm.TYPE, new DummyRealm.Factory(LdapRealm.TYPE, false)); + factories.put(LdapRealm.TYPE, config -> new DummyRealm(LdapRealm.TYPE, config)); assertThat(factories.get("type_0"), notNullValue()); Settings.Builder builder = Settings.builder() .put("path.home", createTempDir()) @@ -205,7 +202,7 @@ public class RealmsTests extends ESTestCase { .put("xpack.security.authc.realms.custom.order", "1"); Settings settings = builder.build(); Environment env = new Environment(settings); - Realms realms = new Realms(settings, env, factories, securityLicenseState, reservedRealm); + Realms realms = new Realms(settings, env, factories, licenseState, reservedRealm); realms.start(); Iterator iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); @@ -222,7 +219,7 @@ public class RealmsTests extends ESTestCase { } assertThat(types, contains("ldap", "type_0")); - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.DEFAULT); + when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.DEFAULT); iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); realm = iter.next(); @@ -235,7 +232,7 @@ public class RealmsTests extends ESTestCase { } assertThat(i, is(1)); - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.NATIVE); + when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE); iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); realm = iter.next(); @@ -252,7 +249,7 @@ public class RealmsTests extends ESTestCase { } public void testUnlicensedWithNativeRealms() throws Exception { - factories.put(LdapRealm.TYPE, new DummyRealm.Factory(LdapRealm.TYPE, false)); + factories.put(LdapRealm.TYPE, config -> new DummyRealm(LdapRealm.TYPE, config)); final String type = randomFrom(FileRealm.TYPE, NativeRealm.TYPE); Settings.Builder builder = Settings.builder() .put("path.home", createTempDir()) @@ -262,7 +259,7 @@ public class RealmsTests extends ESTestCase { .put("xpack.security.authc.realms.native.order", "1"); Settings settings = builder.build(); Environment env = new Environment(settings); - Realms realms = new Realms(settings, env, factories, securityLicenseState, reservedRealm); + Realms realms = new Realms(settings, env, factories, licenseState, reservedRealm); realms.start(); Iterator iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); @@ -276,7 +273,7 @@ public class RealmsTests extends ESTestCase { assertThat(realm.type(), is(type)); assertThat(iter.hasNext(), is(false)); - when(securityLicenseState.enabledRealmType()).thenReturn(EnabledRealmType.NATIVE); + when(licenseState.allowedRealmType()).thenReturn(AllowedRealmType.NATIVE); iter = realms.iterator(); assertThat(iter.hasNext(), is(true)); realm = iter.next(); @@ -308,7 +305,7 @@ public class RealmsTests extends ESTestCase { } Settings settings = builder.build(); Environment env = new Environment(settings); - Realms realms = new Realms(settings, env, factories, securityLicenseState, reservedRealm); + Realms realms = new Realms(settings, env, factories, licenseState, reservedRealm); realms.start(); Iterator iterator = realms.iterator(); Realm realm = iterator.next(); @@ -346,12 +343,12 @@ public class RealmsTests extends ESTestCase { .put("xpack.security.authc.realms.realm_1.order", 0) .build(); Environment env = new Environment(settings); - Realms realms = new Realms(settings, env, factories, securityLicenseState, reservedRealm); + Realms realms = new Realms(settings, env, factories, licenseState, reservedRealm); realms.start(); assertThat(realms.iterator().hasNext(), is(true)); - when(securityLicenseState.authenticationAndAuthorizationEnabled()).thenReturn(false); + when(licenseState.isAuthAllowed()).thenReturn(false); assertThat(realms.iterator().hasNext(), is(false)); } @@ -385,26 +382,5 @@ public class RealmsTests extends ESTestCase { public boolean userLookupSupported() { return false; } - - static class Factory extends Realm.Factory { - - public Factory(String type, boolean internal) { - super(type, internal); - } - - @Override - public DummyRealm create(RealmConfig config) { - return new DummyRealm(type(), config); - } - - @Override - public DummyRealm createDefault(String name) { - if (type().equals(NativeRealm.TYPE) || type().equals(FileRealm.TYPE)) { - return new DummyRealm(type(), new RealmConfig(name, Settings.EMPTY, - Settings.builder().put("path.home", createTempDir()).build())); - } - return null; - } - } } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java index f14992f5751..fe9ce7eb91f 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java @@ -20,10 +20,10 @@ import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.SecuredStringTests; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import java.util.Collections; import java.util.HashMap; @@ -95,7 +95,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { // let's run as without authorization try { - Map headers = Collections.singletonMap(InternalAuthenticationService.RUN_AS_USER_HEADER, + Map headers = Collections.singletonMap(AuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME); client.filterWithHeader(headers) .admin().cluster().prepareHealth().get(); @@ -108,7 +108,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { Map headers = new HashMap<>(); headers.put("Authorization", UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))); - headers.put(InternalAuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME); + headers.put(AuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME); // lets set the user ClusterHealthResponse response = client.filterWithHeader(headers).admin().cluster().prepareHealth().get(); assertThat(response.isTimedOut(), is(false)); @@ -122,7 +122,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(TRANSPORT_CLIENT_USER, SecuredStringTests.build(SecuritySettingsSource.DEFAULT_PASSWORD))), - new BasicHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME)); + new BasicHeader(AuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME)); fail("request should have failed"); } catch(ResponseException e) { assertThat(e.getResponse().getStatusLine().getStatusCode(), is(403)); @@ -140,13 +140,12 @@ public class RunAsIntegTests extends SecurityIntegTestCase { } // but when running as a different user it should work - try (Response response = getRestClient().performRequest("GET", "/_nodes", + Response response = getRestClient().performRequest("GET", "/_nodes", new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(SecuritySettingsSource.DEFAULT_PASSWORD))), - new BasicHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME))) { - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } + new BasicHeader(AuthenticationService.RUN_AS_USER_HEADER, SecuritySettingsSource.DEFAULT_USER_NAME)); + assertThat(response.getStatusLine().getStatusCode(), is(200)); } public void testEmptyUserImpersonationHeader() throws Exception { @@ -161,7 +160,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { Map headers = new HashMap<>(); headers.put("Authorization", UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))); - headers.put(InternalAuthenticationService.RUN_AS_USER_HEADER, ""); + headers.put(AuthenticationService.RUN_AS_USER_HEADER, ""); client.filterWithHeader(headers).admin().cluster().prepareHealth().get(); fail("run as header should not be allowed to be empty"); @@ -177,7 +176,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(SecuritySettingsSource.DEFAULT_PASSWORD))), - new BasicHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "")); + new BasicHeader(AuthenticationService.RUN_AS_USER_HEADER, "")); fail("request should have failed"); } catch(ResponseException e) { assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401)); @@ -196,7 +195,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { Map headers = new HashMap<>(); headers.put("Authorization", UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))); - headers.put(InternalAuthenticationService.RUN_AS_USER_HEADER, "idontexist"); + headers.put(AuthenticationService.RUN_AS_USER_HEADER, "idontexist"); client.filterWithHeader(headers).admin().cluster().prepareHealth().get(); fail("run as header should not accept non-existent users"); @@ -212,7 +211,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(RUN_AS_USER, SecuredStringTests.build(SecuritySettingsSource.DEFAULT_PASSWORD))), - new BasicHeader(InternalAuthenticationService.RUN_AS_USER_HEADER, "idontexist")); + new BasicHeader(AuthenticationService.RUN_AS_USER_HEADER, "idontexist")); fail("request should have failed"); } catch (ResponseException e) { assertThat(e.getResponse().getStatusLine().getStatusCode(), is(403)); @@ -230,13 +229,10 @@ public class RunAsIntegTests extends SecurityIntegTestCase { Settings settings = Settings.builder() .put(extraSettings) .put("cluster.name", clusterName) - .put(SecurityNettyTransport.SSL_SETTING.getKey(), false) + .put(SecurityNetty3Transport.SSL_SETTING.getKey(), false) .build(); - return TransportClient.builder() - .settings(settings) - .addPlugin(XPackPlugin.class) - .build() + return new XPackTransportClient(settings) .addTransportAddress(publishAddress); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/AbstractActiveDirectoryIntegTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/AbstractActiveDirectoryIntegTests.java index 5d7e57aedf4..ddac569df87 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/AbstractActiveDirectoryIntegTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/AbstractActiveDirectoryIntegTests.java @@ -43,8 +43,7 @@ public class AbstractActiveDirectoryIntegTests extends ESTestCase { } globalSettings = builder.build(); Environment environment = new Environment(globalSettings); - clientSSLService = new ClientSSLService(globalSettings, new Global(globalSettings)); - clientSSLService.setEnvironment(environment); + clientSSLService = new ClientSSLService(globalSettings, environment, new Global(globalSettings), null); } Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, LdapSearchScope scope, diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java index b275c31b210..2a02e94af60 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java @@ -31,7 +31,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("scope", LdapSearchScope.SUB_TREE) .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -48,7 +49,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com") .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, hasItem(containsString("Users"))); } @@ -58,7 +60,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com") .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin"))); } @@ -69,8 +72,9 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-32-545", //Default Users group "S-1-5-21-3510024162-210737641-214529065-513" //Default Domain Users group }; - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } @@ -80,8 +84,9 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-32-545", //Default Users group "S-1-5-21-3510024162-210737641-214529065-513", //Default Domain Users group "S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } @@ -95,9 +100,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-21-3510024162-210737641-214529065-1108", //Geniuses "S-1-5-21-3510024162-210737641-214529065-1106", //SHIELD "S-1-5-21-3510024162-210737641-214529065-1105"};//Avengers - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Bruce Banner, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), - NoOpLogger.INSTANCE); + + final String dn = BRUCE_BANNER_DN; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java index 1c01653718a..e0c48302f72 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java @@ -110,7 +110,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateUserPrincipleName() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateUserPrincipleName", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -122,7 +122,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateSAMAccountName() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateSAMAccountName", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -144,7 +144,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachesSuccesfulAuthentications() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateCachesSuccesfulAuthentications", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -160,7 +160,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachingCanBeDisabled() throws Exception { Settings settings = settings(Settings.builder().put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING, -1).build()); RealmConfig config = new RealmConfig("testAuthenticateCachingCanBeDisabled", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -176,7 +176,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachingClearsCacheOnRoleMapperRefresh() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateCachingClearsCacheOnRoleMapperRefresh", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -203,7 +203,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -217,7 +217,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java index 633685400b7..4347c8e9bdb 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java @@ -29,7 +29,7 @@ public class ActiveDirectoryRealmUsageTests extends AbstractActiveDirectoryInteg .put("load_balance.type", loadBalanceType) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, mock(DnRoleMapper.class)); Map stats = realm.usageStats(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java index 78da9be72a1..5aa133a5482 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java @@ -5,7 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.activedirectory; -import org.elasticsearch.ElasticsearchSecurityException; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory; @@ -19,7 +19,7 @@ import org.elasticsearch.test.junit.annotations.Network; import java.io.IOException; import java.util.List; -import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; @@ -31,7 +31,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI @SuppressWarnings("unchecked") public void testAdAuth() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "ironman"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -49,29 +49,45 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI } } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/499") + public void testNetbiosAuth() throws Exception { + final String adUrl = randomFrom("ldap://54.213.145.20:3268", "ldaps://54.213.145.20:3269", AD_LDAP_URL); + RealmConfig config = new RealmConfig("ad-test", buildAdSettings(adUrl, AD_DOMAIN, false), globalSettings); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); + + String userName = "ades\\ironman"; + try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { + List groups = ldap.groups(); + assertThat(groups, containsInAnyOrder( + containsString("Geniuses"), + containsString("Billionaire"), + containsString("Playboy"), + containsString("Philanthropists"), + containsString("Avengers"), + containsString("SHIELD"), + containsString("CN=Users,CN=Builtin"), + containsString("Domain Users"), + containsString("Supers"))); + } + } + public void testTcpReadTimeout() throws Exception { Settings settings = Settings.builder() .put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false)) + .put("group_search.filter", "(objectClass=*)") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, false) .put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); - try (LdapSession ldap = sessionFactory.session("ironman", SecuredStringTests.build(PASSWORD))) { - // In certain cases we may have a successful bind, but a search should take longer and cause a timeout - ldap.groups(); - fail("The TCP connection should timeout before getting groups back"); - } catch (ElasticsearchSecurityException e) { - assertAuthenticationException(e); - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting")); - } + LDAPException expected = expectThrows(LDAPException.class, + () -> sessionFactory.session("ironman", SecuredStringTests.build(PASSWORD)).groups()); + assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } public void testAdAuthAvengers() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; for(String user: users) { @@ -86,7 +102,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -108,7 +124,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Bruce Banner, CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.BASE, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -134,7 +150,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .put(ActiveDirectorySessionFactory.AD_GROUP_SEARCH_SCOPE_SETTING, LdapSearchScope.BASE) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -149,7 +165,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //Login with the UserPrincipalName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -167,7 +183,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //login with sAMAccountName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -191,7 +207,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI "(&(objectclass=user)(userPrincipalName={0}@ad.test.elasticsearch.com))") .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //Login with the UserPrincipalName try (LdapSession ldap = sessionFactory.session("erik.selvig", SecuredStringTests.build(PASSWORD))) { @@ -217,7 +233,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .build(); } RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { @@ -243,7 +259,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .build(); } RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { @@ -259,14 +275,12 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI public void testAdAuthWithHostnameVerification() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, true), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "ironman"; - try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { - fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any active directory servers")); - } + LDAPException expected = + expectThrows(LDAPException.class, () -> sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } public void testStandardLdapHostnameVerification() throws Exception { @@ -277,14 +291,11 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .put(LdapSessionFactory.HOSTNAME_VERIFICATION_SETTING, true) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; - try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { - fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index 87045187270..59ed23be55e 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -16,9 +16,8 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.test.NativeRealmIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.security.SecurityTemplateService; -import org.elasticsearch.xpack.security.authc.esnative.ESNativeRealmMigrateTool; import org.elasticsearch.xpack.security.client.SecurityClient; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; import org.junit.BeforeClass; import java.util.HashSet; @@ -45,7 +44,7 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { Settings s = Settings.builder() .put(super.nodeSettings(nodeOrdinal)) .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), useSSL) + .put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), useSSL) .build(); return s; } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java index 2c652d22679..15a01289b88 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java @@ -42,7 +42,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase { RoleDescriptor rd = new RoleDescriptor("rolename", cluster, ips, runAs); assertThat(ESNativeRealmMigrateTool.MigrateUserOrRoles.createRoleJson(rd), equalTo("{\"cluster\":[],\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," + - "\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[]}")); + "\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[],\"metadata\":{}}")); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 67dd7dcc19a..3d162918cfa 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -55,7 +55,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; /** - * Tests for the ESNativeUsersStore and ESNativeRolesStore + * Tests for the NativeUsersStore and NativeRolesStore */ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { @@ -144,12 +144,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { SecurityClient c = securityClient(); final List existingRoles = Arrays.asList(c.prepareGetRoles().get().roles()); final int existing = existingRoles.size(); + final Map metadata = Collections.singletonMap("key", (Object) randomAsciiOfLengthBetween(1, 10)); logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) + .metadata(metadata) .get(); logger.error("--> waiting for .security index"); ensureGreen(SecurityTemplateService.SECURITY_INDEX_NAME); @@ -158,6 +160,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { assertTrue("role should exist", resp.hasRoles()); RoleDescriptor testRole = resp.roles()[0]; assertNotNull(testRole); + assertThat(testRole.getMetadata().size(), is(1)); + assertThat(testRole.getMetadata().get("key"), is(metadata.get("key"))); c.preparePutRole("test_role2") .cluster("all", "none") diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java index 808b6420eff..d08e1758074 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java @@ -40,13 +40,11 @@ public abstract class GroupsResolverTestCase extends ESTestCase { } Settings settings = builder.build(); Environment env = new Environment(settings); - ClientSSLService clientSSLService = new ClientSSLService(settings, new Global(settings)); - clientSSLService.setEnvironment(env); + ClientSSLService clientSSLService = new ClientSSLService(settings, env, new Global(settings), null); LDAPURL ldapurl = new LDAPURL(ldapUrl()); LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setFollowReferrals(true); - options.setAutoReconnect(true); options.setAllowConcurrentSocketFactoryUse(true); options.setConnectTimeoutMillis(Math.toIntExact(SessionFactory.TIMEOUT_DEFAULT.millis())); options.setResponseTimeoutMillis(SessionFactory.TIMEOUT_DEFAULT.millis()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index 56588941cfe..a99d64373bf 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -66,7 +66,7 @@ public class LdapRealmTests extends LdapTestCase { String userTemplate = VALID_USER_TEMPLATE; Settings settings = buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -82,7 +82,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -98,7 +98,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -116,7 +116,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); DnRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, roleMapper); @@ -143,7 +143,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -164,7 +164,7 @@ public class LdapRealmTests extends LdapTestCase { .put(HOSTNAME_VERIFICATION_SETTING, false) .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - SessionFactory sessionFactory = LdapRealm.Factory.sessionFactory(config, null); + SessionFactory sessionFactory = LdapRealm.sessionFactory(config, null); assertThat(sessionFactory, is(instanceOf(LdapSessionFactory.class))); } @@ -180,7 +180,7 @@ public class LdapRealmTests extends LdapTestCase { .put(HOSTNAME_VERIFICATION_SETTING, false) .build(); RealmConfig config = new RealmConfig("test-ldap-realm-user-search", settings, globalSettings); - SessionFactory sessionFactory = LdapRealm.Factory.sessionFactory(config, null); + SessionFactory sessionFactory = LdapRealm.sessionFactory(config, null); try { assertThat(sessionFactory, is(instanceOf(LdapUserSearchSessionFactory.class))); } finally { @@ -199,7 +199,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm-user-search", settings, globalSettings); try { - LdapRealm.Factory.sessionFactory(config, null); + LdapRealm.sessionFactory(config, null); fail("an exception should have been thrown because both user template and user search settings were specified"); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), containsString("settings were found for both user search and user template")); @@ -216,7 +216,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm-userdn", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null)); User user = ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", SecuredStringTests.build(PASSWORD))); @@ -244,7 +244,7 @@ public class LdapRealmTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap-realm", settings.build(), globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm realm = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null)); Map stats = realm.usageStats(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java index f4550255b1a..c6c161d9ec4 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java @@ -6,8 +6,8 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPURL; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -19,9 +19,9 @@ import org.elasticsearch.xpack.security.authc.support.SecuredStringTests; import org.elasticsearch.test.junit.annotations.Network; import org.junit.Before; -import java.io.IOException; import java.util.List; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -49,7 +49,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -57,15 +57,15 @@ public class LdapSessionFactoryTests extends LdapTestCase { try (LdapSession session = sessionFactory.session(user, userPass)) { fail("expected connection timeout error here"); } catch (Exception e) { - assertThat(e, instanceOf(ElasticsearchSecurityException.class)); - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting ")); + assertThat(e, instanceOf(LDAPException.class)); + assertThat(e.getMessage(), containsString("A client-side timeout was encountered while waiting ")); } finally { ldapServer.setProcessingDelayMillis(0L); } } @Network - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch-shield/issues/767") + @AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2849") public void testConnectTimeout() { // Local sockets connect too fast... String ldapUrl = "ldap://54.200.235.244:389"; @@ -78,19 +78,17 @@ public class LdapSessionFactoryTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); long start = System.currentTimeMillis(); - try (LdapSession session = sessionFactory.session(user, userPass)) { - fail("expected connection timeout error here"); - } catch (Exception e) { - long time = System.currentTimeMillis() - start; - assertThat(time, lessThan(10000L)); - assertThat(e, instanceOf(IOException.class)); - assertThat(e.getCause().getCause().getMessage(), containsString("within the configured timeout of")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, userPass)); + long time = System.currentTimeMillis() - start; + assertThat(time, lessThan(10000L)); + assertThat(expected, instanceOf(LDAPException.class)); + assertThat(expected.getCause().getMessage(), + anyOf(containsString("within the configured timeout of"), containsString("connect timed out"))); } public void testBindWithTemplates() throws Exception { @@ -103,7 +101,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -124,15 +122,14 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); - try (LdapSession ldapConnection = ldapFac.session(user, userPass)) { - fail("Expected ElasticsearchSecurityException"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), is("failed LDAP authentication")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> ldapFac.session(user, userPass)); + assertThat(expected.getMessage(), containsString("Unable to bind as user")); + Throwable[] suppressed = expected.getSuppressed(); + assertThat(suppressed.length, is(2)); } public void testGroupLookupSubtree() throws Exception { @@ -141,7 +138,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -158,7 +155,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; try (LdapSession ldap = ldapFac.session(user, SecuredStringTests.build("pass"))) { @@ -173,7 +170,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java index 03db6a4ee15..60dfe8bbbe3 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java @@ -5,12 +5,12 @@ */ package org.elasticsearch.xpack.security.authc.ldap; -import com.carrotsearch.randomizedtesting.ThreadFilter; -import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; +import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.sdk.BindRequest; import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnectionPool; import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; +import com.unboundid.ldap.sdk.LDAPURL; import com.unboundid.ldap.sdk.SimpleBindRequest; import com.unboundid.ldap.sdk.SingleServerSet; import org.elasticsearch.ElasticsearchSecurityException; @@ -18,8 +18,6 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.env.Environment; -import org.elasticsearch.node.MockNode; -import org.elasticsearch.node.Node; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.activedirectory.ActiveDirectorySessionFactoryTests; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -31,17 +29,12 @@ import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; import org.elasticsearch.xpack.security.support.NoOpLogger; import org.elasticsearch.test.junit.annotations.Network; -import org.elasticsearch.xpack.watcher.Watcher; -import org.elasticsearch.xpack.XPackPlugin; import org.junit.Before; -import java.io.IOException; import java.nio.file.Path; import java.text.MessageFormat; -import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -51,12 +44,6 @@ import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -// thread leak filter for UnboundID's background connect threads. The background connect threads do not always respect the -// timeout and linger. Will be fixed in a new version of the library, see -// http://sourceforge.net/p/ldap-sdk/discussion/1001257/thread/154e3b71/ -@ThreadLeakFilters(filters = { - LdapUserSearchSessionFactoryTests.BackgroundConnectThreadLeakFilter.class -}) public class LdapUserSearchSessionFactoryTests extends LdapTestCase { private ClientSSLService clientSSLService; @@ -75,8 +62,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("xpack.security.ssl.keystore.path", keystore) .put("xpack.security.ssl.keystore.password", "changeit") .build(); - clientSSLService = new ClientSSLService(settings, new Global(settings)); - clientSSLService.setEnvironment(env); + clientSSLService = new ClientSSLService(settings, env, new Global(settings), null); globalSettings = Settings.builder().put("path.home", createTempDir()).build(); } @@ -88,9 +74,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); try { assertThat(sessionFactory.supportsUnauthenticatedSession(), is(true)); } finally { @@ -108,9 +95,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -143,9 +131,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.BASE) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -182,9 +171,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.BASE) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -217,9 +207,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.ONE_LEVEL) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -256,9 +247,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .put("user_search.scope", LdapSearchScope.ONE_LEVEL) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -290,9 +282,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") .put("user_search.attribute", "uid1") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "William Bush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -327,9 +320,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("user_search.base_dn", userSearchBase) .put("bind_dn", "cn=Horatio Hornblower,ou=people,o=sevenSeas") .put("bind_password", "pass") + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, null); String user = "wbush"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -362,9 +356,10 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_dn", "ironman@ad.test.elasticsearch.com") .put("bind_password", ActiveDirectorySessionFactoryTests.PASSWORD) .put("user_search.attribute", "cn") + .put("user_search.pool.enabled", randomBoolean()) .build(); RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try { @@ -404,8 +399,9 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("user_search.base_dn", userSearchBase) .put("bind_dn", "uid=blackwidow,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com") .put("bind_password", OpenLdapTests.PASSWORD) + .put("user_search.pool.enabled", randomBoolean()) .build(), globalSettings); - LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService).init(); + LdapUserSearchSessionFactory sessionFactory = new LdapUserSearchSessionFactory(config, clientSSLService); String[] users = new String[] { "cap", "hawkeye", "hulk", "ironman", "thor" }; try { @@ -480,7 +476,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { } } - public void testThatEmptyBindDNThrowsExceptionWithHealthCheckEnabled() throws Exception { + public void testThatEmptyBindDNWithHealthCheckEnabledDoesNotThrow() throws Exception { String groupSearchBase = "o=sevenSeas"; String userSearchBase = "o=sevenSeas"; RealmConfig config = new RealmConfig("ldap_realm", Settings.builder() @@ -489,12 +485,13 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("bind_password", "pass") .build(), globalSettings); + LdapUserSearchSessionFactory searchSessionFactory = null; try { - new LdapUserSearchSessionFactory(config, null).init(); - fail("expected an exception"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("[bind_dn] has not been specified so a value must be specified for [user_search" + - ".pool.health_check.dn] or [user_search.pool.health_check.enabled] must be set to false")); + searchSessionFactory = new LdapUserSearchSessionFactory(config, null); + } finally { + if (searchSessionFactory != null) { + searchSessionFactory.shutdown(); + } } } @@ -513,12 +510,17 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { assertThat(simpleBindRequest.getBindDN(), is("cn=ironman")); } - @Network - public void testThatLDAPServerConnectErrorDoesNotPreventNodeFromStarting() throws IOException { + public void testThatConnectErrorIsNotThrownOnConstruction() throws Exception { String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com"; String userSearchBase = "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; + + // pick a random ldap server and stop it + InMemoryDirectoryServer inMemoryDirectoryServer = randomFrom(ldapServers); + String ldapUrl = new LDAPURL("ldap", "localhost", inMemoryDirectoryServer.getListenPort(), null, null, null, null).toString(); + inMemoryDirectoryServer.shutDown(true); + Settings ldapSettings = Settings.builder() - .put(LdapTestCase.buildLdapSettings(new String[] { "ldaps://elastic.co:636" }, Strings.EMPTY_ARRAY, + .put(LdapTestCase.buildLdapSettings(new String[] { ldapUrl }, Strings.EMPTY_ARRAY, groupSearchBase, LdapSearchScope.SUB_TREE)) .put("user_search.base_dn", userSearchBase) .put("bind_dn", "ironman@ad.test.elasticsearch.com") @@ -526,30 +528,18 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase { .put("user_search.attribute", "cn") .put("timeout.tcp_connect", "500ms") .put("type", "ldap") + .put("user_search.pool.health_check.enabled", false) + .put("user_search.pool.enabled", randomBoolean()) .build(); - Settings.Builder builder = Settings.builder(); - for (Map.Entry entry : ldapSettings.getAsMap().entrySet()) { - builder.put("xpack.security.authc.realms.ldap1." + entry.getKey(), entry.getValue()); - } - builder.put("path.home", createTempDir()); - - // disable watcher, because watcher takes some time when starting, which results in problems - // having a quick start/stop cycle like below - builder.put(XPackPlugin.featureEnabledSetting(Watcher.NAME), false); - - try (Node node = new MockNode(builder.build(), Collections.singletonList(XPackPlugin.class))) { - node.start(); - } - } - - public static class BackgroundConnectThreadLeakFilter implements ThreadFilter { - @Override - public boolean reject(Thread thread) { - if (thread.getName().startsWith("Background connect thread for elastic.co")) { - return true; + RealmConfig config = new RealmConfig("ldap_realm", ldapSettings, globalSettings); + LdapUserSearchSessionFactory searchSessionFactory = null; + try { + searchSessionFactory = new LdapUserSearchSessionFactory(config, null); + } finally { + if (searchSessionFactory != null) { + searchSessionFactory.shutdown(); } - return false; } } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java index dace77af176..66491145d82 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -25,6 +26,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Map; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasItem; @@ -58,8 +60,7 @@ public class OpenLdapTests extends ESTestCase { } globalSettings = builder.build(); Environment environment = new Environment(globalSettings); - clientSSLService = new ClientSSLService(globalSettings, new Global(globalSettings)); - clientSSLService.setEnvironment(environment); + clientSSLService = new ClientSSLService(globalSettings, environment, new Global(globalSettings), null); } public void testConnect() throws Exception { @@ -68,7 +69,7 @@ public class OpenLdapTests extends ESTestCase { String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; RealmConfig config = new RealmConfig("oldap-test", buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; for (String user : users) { @@ -85,7 +86,7 @@ public class OpenLdapTests extends ESTestCase { String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; RealmConfig config = new RealmConfig("oldap-test", buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" }; for (String user : users) { @@ -112,7 +113,7 @@ public class OpenLdapTests extends ESTestCase { settings.put("load_balance.type", loadBalanceType); RealmConfig config = new RealmConfig("oldap-test", settings.build(), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); LdapRealm realm = new LdapRealm(config, sessionFactory, mock(DnRoleMapper.class)); Map stats = realm.usageStats(); @@ -134,32 +135,29 @@ public class OpenLdapTests extends ESTestCase { .put("group_search.user_attribute", "uid") .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); try (LdapSession ldap = sessionFactory.session("selvig", SecuredStringTests.build(PASSWORD))){ assertThat(ldap.groups(), hasItem(containsString("Geniuses"))); } } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/499") + @AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2849") public void testTcpTimeout() throws Exception { String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; Settings settings = Settings.builder() - .put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL)) + .put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE)) + .put("group_search.filter", "(objectClass=*)") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, false) .put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); - try (LdapSession ldap = sessionFactory.session("thor", SecuredStringTests.build(PASSWORD))) { - // In certain cases we may have a successful bind, but a search should take longer and cause a timeout - ldap.groups(); - fail("The TCP connection should timeout before getting groups back"); - } catch (ElasticsearchException e) { - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting")); - } + LDAPException expected = expectThrows(LDAPException.class, + () -> sessionFactory.session("thor", SecuredStringTests.build(PASSWORD)).groups()); + assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } public void testStandardLdapConnectionHostnameVerification() throws Exception { @@ -172,14 +170,11 @@ public class OpenLdapTests extends ESTestCase { .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "blackwidow"; - try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { - fail("OpenLDAP certificate does not contain the correct hostname/ip so hostname verification should fail on open"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, LdapSearchScope scope) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java index df0ec6d60a0..7a0c638a5c8 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.authc.ldap; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -31,7 +30,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -46,7 +45,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -61,7 +60,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Avengers"))); } @@ -74,7 +73,19 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { SearchGroupsResolver resolver = new SearchGroupsResolver(settings); List groups = resolver.resolve(ldapConnection, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", - TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); + assertThat(groups, hasItem(containsString("Geniuses"))); + } + + public void testFilterIncludesPosixGroups() throws Exception { + Settings settings = Settings.builder() + .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("user_attribute", "uid") + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapConnection, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Geniuses"))); } @@ -116,12 +127,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .put("user_attribute", "doesntExists") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - try { - resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE); - fail("searching for a non-existing attribute should throw an LdapException"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), containsString("no results returned")); - } + assertNull(resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE)); } public void testReadBinaryUserAttribute() throws Exception { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java index f297c920e7e..97a43ad228e 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java @@ -26,7 +26,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { public void testResolve() throws Exception { //falling back on the 'memberOf' attribute UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(Settings.EMPTY); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -39,7 +39,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { .put("user_group_attribute", "seeAlso") .build(); UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, hasItems(containsString("Avengers"))); //seeAlso only has Avengers } @@ -48,7 +48,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { .put("user_group_attribute", "doesntExist") .build(); UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, empty()); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java index 2201bff81f7..3e9f855e689 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java @@ -166,7 +166,7 @@ public class SessionFactoryLoadBalancingTests extends LdapTestCase { LdapSearchScope.SUB_TREE, loadBalancing); RealmConfig config = new RealmConfig("test-session-factory", settings, Settings.builder().put("path.home", createTempDir()).build()); - return new TestSessionFactory(config, null).init(); + return new TestSessionFactory(config, null); } static class TestSessionFactory extends SessionFactory { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java index 5d6a87f88dc..cf78c0d5302 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java @@ -63,6 +63,6 @@ public class SessionFactoryTests extends ESTestCase { protected LdapSession getSession(String user, SecuredString password) { return null; } - }.init(); + }; } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java index 16e8b4411fb..22229227eef 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java @@ -21,11 +21,11 @@ import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.file.FileRealm; import org.elasticsearch.xpack.security.transport.SSLClientAuth; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -56,8 +56,8 @@ public class PkiAuthenticationTests extends SecurityIntegTestCase { .put(super.nodeSettings(nodeOrdinal)) .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), true) - .put(SecurityNettyHttpServerTransport.CLIENT_AUTH_SETTING.getKey(), sslClientAuth) + .put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty3HttpServerTransport.CLIENT_AUTH_SETTING.getKey(), sslClientAuth) .put("xpack.security.authc.realms.file.type", FileRealm.TYPE) .put("xpack.security.authc.realms.file.order", "0") .put("xpack.security.authc.realms.pki1.type", PkiRealm.TYPE) @@ -155,7 +155,7 @@ public class PkiAuthenticationTests extends SecurityIntegTestCase { .put("cluster.name", internalCluster().getClusterName()); builder.remove(Security.USER_SETTING.getKey()); builder.remove("request.headers.Authorization"); - return TransportClient.builder().settings(builder).addPlugin(XPackPlugin.class).build(); + return new XPackTransportClient(builder.build()); } private String getNodeUrl() { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java index baff71c54a0..58b15f5a289 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiOptionalClientAuthTests.java @@ -5,12 +5,11 @@ */ package org.elasticsearch.xpack.security.authc.pki; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.message.BasicHeader; +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.SSLSocketFactoryHttpConfigCallback; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; @@ -18,13 +17,13 @@ import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.transport.SSLClientAuth; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.junit.BeforeClass; import javax.net.ssl.SSLContext; @@ -57,8 +56,8 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { return Settings.builder() .put(super.nodeSettings(nodeOrdinal)) .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), true) - .put(SecurityNettyHttpServerTransport.CLIENT_AUTH_SETTING.getKey(), SSLClientAuth.OPTIONAL) + .put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty3HttpServerTransport.CLIENT_AUTH_SETTING.getKey(), SSLClientAuth.OPTIONAL) .put("xpack.security.authc.realms.file.type", "file") .put("xpack.security.authc.realms.file.order", "0") .put("xpack.security.authc.realms.pki1.type", "pki") @@ -79,8 +78,8 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { } public void testRestClientWithoutClientCertificate() throws Exception { - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(getSSLContext()); - try (RestClient restClient = createRestClient(new SSLSocketFactoryHttpConfigCallback(sslConnectionSocketFactory), "https")) { + SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(getSSLContext()); + try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) { try { restClient.performRequest("GET", "_nodes"); fail("request should have failed"); @@ -88,12 +87,11 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { assertThat(e.getResponse().getStatusLine().getStatusCode(), is(401)); } - try (Response response = restClient.performRequest("GET", "_nodes", + Response response = restClient.performRequest("GET", "_nodes", new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, - new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))))) { - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } + new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray())))); + assertThat(response.getStatusLine().getStatusCode(), is(200)); } } @@ -108,11 +106,11 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { .put(sslSettingsForStore) .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) .put("cluster.name", internalCluster().getClusterName()) - .put(SecurityNettyTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty3Transport.SSL_SETTING.getKey(), true) .build(); - try (TransportClient client = TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build()) { + try (TransportClient client = new XPackTransportClient(settings)) { client.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), port)); assertGreenClusterState(client); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutClientAuthenticationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutClientAuthenticationTests.java index bfa82ffadb7..7a21022f6e8 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutClientAuthenticationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutClientAuthenticationTests.java @@ -6,12 +6,11 @@ package org.elasticsearch.xpack.security.authc.pki; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.message.BasicHeader; +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; import org.elasticsearch.client.Client; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.SSLSocketFactoryHttpConfigCallback; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.test.ESIntegTestCase.ClusterScope; @@ -20,8 +19,8 @@ import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.transport.SSLClientAuth; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyHttpServerTransport; -import org.elasticsearch.xpack.security.transport.netty.SecurityNettyTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; +import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; @@ -61,9 +60,9 @@ public class PkiWithoutClientAuthenticationTests extends SecurityIntegTestCase { return Settings.builder() .put(super.nodeSettings(nodeOrdinal)) .put(NetworkModule.HTTP_ENABLED.getKey(), true) - .put(SecurityNettyTransport.CLIENT_AUTH_SETTING.getKey(), false) - .put(SecurityNettyHttpServerTransport.SSL_SETTING.getKey(), true) - .put(SecurityNettyHttpServerTransport.CLIENT_AUTH_SETTING.getKey(), + .put(SecurityNetty3Transport.CLIENT_AUTH_SETTING.getKey(), false) + .put(SecurityNetty3HttpServerTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty3HttpServerTransport.CLIENT_AUTH_SETTING.getKey(), randomFrom(SSLClientAuth.NO.name(), false, "false", "FALSE", SSLClientAuth.NO.name().toLowerCase(Locale.ROOT))) .put("xpack.security.authc.realms.pki1.type", "pki") .put("xpack.security.authc.realms.pki1.order", "0") @@ -78,14 +77,13 @@ public class PkiWithoutClientAuthenticationTests extends SecurityIntegTestCase { public void testThatHttpWorks() throws Exception { SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new SecureRandom()); - SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sc); - try (RestClient restClient = createRestClient(new SSLSocketFactoryHttpConfigCallback(sslConnectionSocketFactory), "https")) { - try (Response response = restClient.performRequest("GET", "/_nodes", + SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(sc); + try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) { + Response response = restClient.performRequest("GET", "/_nodes", new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, - new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))))) { - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } + new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray())))); + assertThat(response.getStatusLine().getStatusCode(), is(200)); } } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutSSLTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutSSLTests.java index b1c691ecc35..375acafc2cf 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutSSLTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiWithoutSSLTests.java @@ -41,11 +41,10 @@ public class PkiWithoutSSLTests extends SecurityIntegTestCase { } public void testThatHttpWorks() throws Exception { - try (Response response = getRestClient().performRequest("GET", "/_nodes", + Response response = getRestClient().performRequest("GET", "/_nodes", new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.DEFAULT_USER_NAME, - new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray()))))) { - assertThat(response.getStatusLine().getStatusCode(), is(200)); - } + new SecuredString(SecuritySettingsSource.DEFAULT_PASSWORD.toCharArray())))); + assertThat(response.getStatusLine().getStatusCode(), is(200)); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java index 09b1e12852b..bd45c406c46 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealmTests.java @@ -142,7 +142,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase { } public void testAuthenticateContract() throws Exception { - Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings); + Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings); User user = realm.authenticate(new UsernamePasswordToken("user", SecuredStringTests.build("pass"))); assertThat(user , nullValue()); @@ -152,7 +152,7 @@ public class CachingUsernamePasswordRealmTests extends ESTestCase { } public void testLookupContract() throws Exception { - Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings); + Realm realm = new FailingAuthenticationRealm(Settings.EMPTY, globalSettings); User user = realm.lookupUser("user"); assertThat(user , nullValue()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java index 6d95614ade5..ae5703611a2 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java @@ -190,7 +190,8 @@ public class DnRoleMapperTests extends ESTestCase { } public void testParseFile_Empty() throws Exception { - Path file = createTempFile(); + Path file = createTempDir().resolve("foo.yaml"); + Files.createFile(file); CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.INFO); Map> mappings = DnRoleMapper.parseFile(file, logger, "_type", "_name"); assertThat(mappings, notNullValue()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/InternalAuthorizationServiceTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java similarity index 85% rename from elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/InternalAuthorizationServiceTests.java rename to elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 60a673249b2..0e0d2570af2 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/InternalAuthorizationServiceTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -5,6 +5,9 @@ */ package org.elasticsearch.xpack.security.authz; +import java.util.ArrayList; +import java.util.List; + import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; @@ -45,47 +48,42 @@ import org.elasticsearch.action.termvectors.TermVectorsAction; import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.update.UpdateAction; import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.search.action.SearchTransportService; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.xpack.security.SecurityTemplateService; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; import org.elasticsearch.xpack.security.user.AnonymousUser; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.XPackUser; -import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; -import org.elasticsearch.xpack.security.authz.store.RolesStore; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportRequest; import org.junit.After; import org.junit.Before; -import java.util.ArrayList; -import java.util.List; - import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthorizationException; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -93,27 +91,25 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -public class InternalAuthorizationServiceTests extends ESTestCase { - private AuditTrail auditTrail; - private RolesStore rolesStore; +public class AuthorizationServiceTests extends ESTestCase { + private AuditTrailService auditTrail; + private CompositeRolesStore rolesStore; private ClusterService clusterService; - private InternalAuthorizationService internalAuthorizationService; + private AuthorizationService authorizationService; private ThreadContext threadContext; private ThreadPool threadPool; @Before public void setup() { - rolesStore = mock(RolesStore.class); + rolesStore = mock(CompositeRolesStore.class); clusterService = mock(ClusterService.class); - auditTrail = mock(AuditTrail.class); + auditTrail = mock(AuditTrailService.class); threadContext = new ThreadContext(Settings.EMPTY); threadPool = mock(ThreadPool.class); when(threadPool.getThreadContext()).thenReturn(threadContext); - IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class); - when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg()); - internalAuthorizationService = new InternalAuthorizationService(Settings.EMPTY, rolesStore, clusterService, - auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver); + authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService, + auditTrail, new DefaultAuthenticationFailureHandler(), threadPool); } @After @@ -125,10 +121,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase { TransportRequest request = mock(TransportRequest.class); // A failure would throw an exception - internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:monitor/whatever", request); + authorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:monitor/whatever", request); verify(auditTrail).accessGranted(SystemUser.INSTANCE, "indices:monitor/whatever", request); - internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "internal:whatever", request); + authorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "internal:whatever", request); verify(auditTrail).accessGranted(SystemUser.INSTANCE, "internal:whatever", request); verifyNoMoreInteractions(auditTrail); } @@ -136,7 +132,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { public void testIndicesActionsAreNotAuthorized() { TransportRequest request = mock(TransportRequest.class); try { - internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:", request); + authorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "indices:", request); fail("action beginning with indices should have failed"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, @@ -149,7 +145,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { public void testClusterAdminActionsAreNotAuthorized() { TransportRequest request = mock(TransportRequest.class); try { - internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "cluster:admin/whatever", request); + authorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "cluster:admin/whatever", request); fail("action beginning with cluster:admin/whatever should have failed"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, @@ -162,7 +158,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { public void testClusterAdminSnapshotStatusActionIsNotAuthorized() { TransportRequest request = mock(TransportRequest.class); try { - internalAuthorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "cluster:admin/snapshot/status", request); + authorizationService.authorize(createAuthentication(SystemUser.INSTANCE), "cluster:admin/snapshot/status", request); fail("action beginning with cluster:admin/snapshot/status should have failed"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [cluster:admin/snapshot/status] is unauthorized for user [" + @@ -176,7 +172,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { TransportRequest request = new SearchRequest(); User user = new User("test user"); try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("user without roles should be denied"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); @@ -189,7 +185,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { TransportRequest request = new SearchRequest(); User user = new User("test user", "non-existent-role"); try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("user with unknown role only should have been denied"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); @@ -204,7 +200,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { when(rolesStore.role("a_all")).thenReturn(Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); try { - internalAuthorizationService.authorize(createAuthentication(user), "whatever", request); + authorizationService.authorize(createAuthentication(user), "whatever", request); fail("non indices and non cluster requests should be denied"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [whatever] is unauthorized for user [test user]")); @@ -219,7 +215,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { when(rolesStore.role("no_indices")).thenReturn(Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build()); try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("user only has cluster roles so indices requests should fail"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); @@ -233,29 +229,29 @@ public class InternalAuthorizationServiceTests extends ESTestCase { when(rolesStore.role("a_all")).thenReturn(Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); - internalAuthorizationService.authorize(createAuthentication(user), ClearScrollAction.NAME, clearScrollRequest); + authorizationService.authorize(createAuthentication(user), ClearScrollAction.NAME, clearScrollRequest); verify(auditTrail).accessGranted(user, ClearScrollAction.NAME, clearScrollRequest); SearchScrollRequest searchScrollRequest = new SearchScrollRequest(); - internalAuthorizationService.authorize(createAuthentication(user), SearchScrollAction.NAME, searchScrollRequest); + authorizationService.authorize(createAuthentication(user), SearchScrollAction.NAME, searchScrollRequest); verify(auditTrail).accessGranted(user, SearchScrollAction.NAME, searchScrollRequest); // We have to use a mock request for other Scroll actions as the actual requests are package private to SearchTransportService TransportRequest request = mock(TransportRequest.class); - internalAuthorizationService + authorizationService .authorize(createAuthentication(user), SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request); - internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); + authorizationService.authorize(createAuthentication(user), SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); - internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); + authorizationService.authorize(createAuthentication(user), SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); - internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); + authorizationService.authorize(createAuthentication(user), SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); - internalAuthorizationService.authorize(createAuthentication(user), SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); + authorizationService.authorize(createAuthentication(user), SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); verify(auditTrail).accessGranted(user, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); verifyNoMoreInteractions(auditTrail); } @@ -269,7 +265,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("indices request for b should be denied since there is no such index"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user]")); @@ -290,7 +286,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); try { - internalAuthorizationService.authorize(createAuthentication(user), CreateIndexAction.NAME, request); + authorizationService.authorize(createAuthentication(user), CreateIndexAction.NAME, request); fail("indices creation request with alias should be denied since user does not have permission to alias"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, @@ -311,7 +307,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); - internalAuthorizationService.authorize(createAuthentication(user), CreateIndexAction.NAME, request); + authorizationService.authorize(createAuthentication(user), CreateIndexAction.NAME, request); verify(auditTrail).accessGranted(user, CreateIndexAction.NAME, request); verifyNoMoreInteractions(auditTrail); @@ -322,7 +318,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { public void testIndicesAliasesWithNoRolesUser() { User user = new User("test user"); - List list = internalAuthorizationService.authorizedIndicesAndAliases(user, ""); + List list = authorizationService.authorizedIndicesAndAliases(user, ""); assertThat(list.isEmpty(), is(true)); } @@ -347,7 +343,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { .build(), true) .build()); - List list = internalAuthorizationService.authorizedIndicesAndAliases(user, SearchAction.NAME); + List list = authorizationService.authorizedIndicesAndAliases(user, SearchAction.NAME); assertThat(list, containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab")); assertThat(list.contains("bbbbb"), is(false)); assertThat(list.contains("ba"), is(false)); @@ -357,17 +353,15 @@ public class InternalAuthorizationServiceTests extends ESTestCase { TransportRequest request = new IndicesExistsRequest("b"); ClusterState state = mock(ClusterState.class); AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build()); - IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class); - when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg()); - internalAuthorizationService = new InternalAuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail, - new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver); + authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail, + new DefaultAuthenticationFailureHandler(), threadPool); when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); try { - internalAuthorizationService.authorize(createAuthentication(AnonymousUser.INSTANCE), "indices:a", request); + authorizationService.authorize(createAuthentication(AnonymousUser.INSTANCE), "indices:a", request); fail("indices request for b should be denied since there is no such index"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, @@ -384,21 +378,19 @@ public class InternalAuthorizationServiceTests extends ESTestCase { ClusterState state = mock(ClusterState.class); AnonymousUser.initialize(Settings.builder() .put(AnonymousUser.ROLES_SETTING.getKey(), "a_all") - .put(InternalAuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false) + .put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false) .build()); User anonymousUser = AnonymousUser.INSTANCE; - IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class); - when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg()); - internalAuthorizationService = new InternalAuthorizationService( - Settings.builder().put(InternalAuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false).build(), - rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver); + authorizationService = new AuthorizationService( + Settings.builder().put(AuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false).build(), + rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool); when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); try { - internalAuthorizationService.authorize(createAuthentication(anonymousUser), "indices:a", request); + authorizationService.authorize(createAuthentication(anonymousUser), "indices:a", request); fail("indices request for b should be denied since there is no such index"); } catch (ElasticsearchSecurityException e) { assertAuthenticationException(e, containsString("action [indices:a] requires authentication")); @@ -414,7 +406,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { User user = new User("test user", null, new User("run as me", new String[] { "admin" })); assertThat(user.runAs(), is(notNullValue())); try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("user without roles should be denied for run as"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]")); @@ -434,7 +426,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { .build()); try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("user without roles should be denied for run as"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]")); @@ -468,7 +460,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { } try { - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); fail("the run as user's role doesn't exist so they should not get authorized"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [indices:a] is unauthorized for user [test user] run as [run as me]")); @@ -499,7 +491,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { .add(IndexPrivilege.ALL, "b") .build()); - internalAuthorizationService.authorize(createAuthentication(user), "indices:a", request); + authorizationService.authorize(createAuthentication(user), "indices:a", request); verify(auditTrail).runAsGranted(user, "indices:a", request); verify(auditTrail).accessGranted(user, "indices:a", request); verifyNoMoreInteractions(auditTrail); @@ -538,7 +530,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { String action = requestTuple.v1(); TransportRequest request = requestTuple.v2(); try { - internalAuthorizationService.authorize(createAuthentication(user), action, request); + authorizationService.authorize(createAuthentication(user), action, request); fail("only the xpack user can execute operation [" + action + "] against the internal index"); } catch (ElasticsearchSecurityException e) { assertAuthorizationException(e, containsString("action [" + action + "] is unauthorized for user [all_access_user]")); @@ -549,12 +541,12 @@ public class InternalAuthorizationServiceTests extends ESTestCase { // we should allow waiting for the health of the index or any index if the user has this permission ClusterHealthRequest request = new ClusterHealthRequest(SecurityTemplateService.SECURITY_INDEX_NAME); - internalAuthorizationService.authorize(createAuthentication(user), ClusterHealthAction.NAME, request); + authorizationService.authorize(createAuthentication(user), ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request); // multiple indices request = new ClusterHealthRequest(SecurityTemplateService.SECURITY_INDEX_NAME, "foo", "bar"); - internalAuthorizationService.authorize(createAuthentication(user), ClusterHealthAction.NAME, request); + authorizationService.authorize(createAuthentication(user), ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request); } @@ -586,7 +578,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { for (Tuple requestTuple : requests) { String action = requestTuple.v1(); TransportRequest request = requestTuple.v2(); - internalAuthorizationService.authorize(createAuthentication(user), action, request); + authorizationService.authorize(createAuthentication(user), action, request); verify(auditTrail).accessGranted(user, action, request); } } @@ -620,7 +612,7 @@ public class InternalAuthorizationServiceTests extends ESTestCase { for (Tuple requestTuple : requests) { String action = requestTuple.v1(); TransportRequest request = requestTuple.v2(); - internalAuthorizationService.authorize(createAuthentication(XPackUser.INSTANCE), action, request); + authorizationService.authorize(createAuthentication(XPackUser.INSTANCE), action, request); verify(auditTrail).accessGranted(XPackUser.INSTANCE, action, request); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationUtilsTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationUtilsTests.java index 540af489890..fdaa0eb50e5 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationUtilsTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationUtilsTests.java @@ -44,7 +44,7 @@ public class AuthorizationUtilsTests extends ESTestCase { User user = new User(randomAsciiOfLength(6), new String[] {}); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); - threadContext.putTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("indices:foo", "cluster:bar")); + threadContext.putTransient(AuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("indices:foo", "cluster:bar")); assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(true)); } @@ -52,7 +52,7 @@ public class AuthorizationUtilsTests extends ESTestCase { User user = new User(randomAsciiOfLength(6), new String[] {}); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication); - threadContext.putTransient(InternalAuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("internal:foo/bar")); + threadContext.putTransient(AuthorizationService.ORIGINATING_ACTION_KEY, randomFrom("internal:foo/bar")); assertThat(AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, "internal:something"), is(false)); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java index 2022514b8e2..f2bbba529a7 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java @@ -7,10 +7,17 @@ package org.elasticsearch.xpack.security.authz; import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.ByteBufferStreamInput; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.xpack.security.support.MetadataUtils; import org.elasticsearch.test.ESTestCase; +import java.util.Map; + import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.core.Is.is; @@ -37,7 +44,7 @@ public class RoleDescriptorTests extends ESTestCase { }; RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }); assertThat(descriptor.toString(), is("Role[name=test, cluster=[all,none], indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], " + - "privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]")); + "privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo], metadata=[{}]]")); } public void testToXContent() throws Exception { @@ -49,7 +56,8 @@ public class RoleDescriptorTests extends ESTestCase { .query("{\"query\": {\"match_all\": {}}}") .build() }; - RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }); + Map metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null; + RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata); XContentBuilder builder = descriptor.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS); RoleDescriptor parsed = RoleDescriptor.parse("test", builder.bytes()); assertThat(parsed, is(descriptor)); @@ -88,5 +96,34 @@ public class RoleDescriptorTests extends ESTestCase { assertEquals(1, rd.getIndicesPrivileges().length); assertArrayEquals(new String[] { "idx1", "idx2" }, rd.getIndicesPrivileges()[0].getIndices()); assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs()); + + q = "{\"cluster\":[\"a\", \"b\"], \"metadata\":{\"foo\":\"bar\"}}"; + rd = RoleDescriptor.parse("test", new BytesArray(q)); + assertEquals("test", rd.getName()); + assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); + assertEquals(0, rd.getIndicesPrivileges().length); + assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs()); + assertNotNull(rd.getMetadata()); + assertThat(rd.getMetadata().size(), is(1)); + assertThat(rd.getMetadata().get("foo"), is("bar")); + } + + public void testSerialization() throws Exception { + BytesStreamOutput output = new BytesStreamOutput(); + RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder() + .indices("i1", "i2") + .privileges("read") + .fields("body", "title") + .query("{\"query\": {\"match_all\": {}}}") + .build() + }; + Map metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null; + final RoleDescriptor descriptor = + new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata); + RoleDescriptor.writeTo(descriptor, output); + StreamInput streamInput = ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes())); + final RoleDescriptor serialized = RoleDescriptor.readFrom(streamInput); + assertEquals(descriptor, serialized); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java index 7aad4e153ae..92ea5be470a 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java @@ -34,7 +34,8 @@ import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.index.query.QueryShardContext; import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.shard.ShardId; -import org.elasticsearch.xpack.security.SecurityLicenseState; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; @@ -55,13 +56,14 @@ public class SecurityIndexSearcherWrapperIntegrationTests extends ESTestCase { public void testDLS() throws Exception { ShardId shardId = new ShardId("_index", "_na_", 0); MapperService mapperService = mock(MapperService.class); + ScriptService scriptService = mock(ScriptService.class); when(mapperService.docMappers(anyBoolean())).thenReturn(Collections.emptyList()); when(mapperService.simpleMatchToIndexNames(anyString())) .then(invocationOnMock -> Collections.singletonList((String) invocationOnMock.getArguments()[0])); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, null, - singleton(new BytesArray("{}"))); + singleton(new BytesArray("{\"match_all\" : {}}"))); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(shardId.getIndex(), Settings.EMPTY); QueryShardContext queryShardContext = mock(QueryShardContext.class); QueryParseContext queryParseContext = mock(QueryParseContext.class); @@ -76,10 +78,10 @@ public class SecurityIndexSearcherWrapperIntegrationTests extends ESTestCase { } }); - SecurityLicenseState licenseState = mock(SecurityLicenseState.class); - when(licenseState.documentAndFieldLevelSecurityEnabled()).thenReturn(true); + XPackLicenseState licenseState = mock(XPackLicenseState.class); + when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true); SecurityIndexSearcherWrapper wrapper = new SecurityIndexSearcherWrapper(indexSettings, queryShardContext, mapperService, - bitsetFilterCache, threadContext, licenseState) { + bitsetFilterCache, threadContext, licenseState, scriptService) { @Override protected QueryShardContext copyQueryShardContext(QueryShardContext context) { @@ -140,7 +142,7 @@ public class SecurityIndexSearcherWrapperIntegrationTests extends ESTestCase { ParsedQuery parsedQuery = new ParsedQuery(new TermQuery(new Term("field", values[i]))); when(queryShardContext.newParseContext(any(XContentParser.class))).thenReturn(queryParseContext); when(queryParseContext.parseInnerQueryBuilder()) - .thenReturn(Optional.of((QueryBuilder) new TermQueryBuilder("field", values[i]))); + .thenReturn(Optional.of(new TermQueryBuilder("field", values[i]))); when(queryShardContext.toQuery(any(QueryBuilder.class))).thenReturn(parsedQuery); DirectoryReader wrappedDirectoryReader = wrapper.wrap(directoryReader); IndexSearcher indexSearcher = wrapper.wrap(new IndexSearcher(wrappedDirectoryReader)); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java index af998349d5a..cd7d878b4ea 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java @@ -35,11 +35,13 @@ import org.apache.lucene.util.BitSet; import org.apache.lucene.util.FixedBitSet; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.SparseFixedBitSet; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.compress.CompressedXContent; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexSettings; @@ -47,21 +49,31 @@ import org.elasticsearch.index.analysis.AnalysisService; import org.elasticsearch.index.cache.bitset.BitsetFilterCache; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.internal.ParentFieldMapper; +import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.similarity.SimilarityService; import org.elasticsearch.indices.IndicesModule; +import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptContext; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.LeafBucketCollector; import org.elasticsearch.xpack.security.authz.accesscontrol.DocumentSubsetReader.DocumentSubsetDirectoryReader; -import org.elasticsearch.xpack.security.SecurityLicenseState; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; +import org.elasticsearch.xpack.security.user.User; import org.junit.After; import org.junit.Before; +import org.mockito.ArgumentCaptor; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Map; import java.util.Set; import static java.util.Collections.emptyList; @@ -75,21 +87,27 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { private ThreadContext threadContext; private MapperService mapperService; + private ScriptService scriptService; private SecurityIndexSearcherWrapper securityIndexSearcherWrapper; private ElasticsearchDirectoryReader esIn; - private SecurityLicenseState licenseState; + private XPackLicenseState licenseState; private IndexSettings indexSettings; @Before public void before() throws Exception { Index index = new Index("_index", "testUUID"); + scriptService = mock(ScriptService.class); indexSettings = IndexSettingsModule.newIndexSettings(index, Settings.EMPTY); AnalysisService analysisService = new AnalysisService(indexSettings, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); @@ -98,8 +116,8 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { new IndicesModule(new NamedWriteableRegistry(), emptyList()).getMapperRegistry(), () -> null); ShardId shardId = new ShardId(index, 0); - licenseState = mock(SecurityLicenseState.class); - when(licenseState.documentAndFieldLevelSecurityEnabled()).thenReturn(true); + licenseState = mock(XPackLicenseState.class); + when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true); threadContext = new ThreadContext(Settings.EMPTY); IndexShard indexShard = mock(IndexShard.class); when(indexShard.shardId()).thenReturn(shardId); @@ -125,7 +143,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { mapperService.merge("type", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); securityIndexSearcherWrapper = - new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState) { + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService) { @Override protected IndicesAccessControl getIndicesAccessControl() { IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, @@ -154,16 +172,16 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { } public void testWrapReaderWhenFeatureDisabled() throws Exception { - when(licenseState.documentAndFieldLevelSecurityEnabled()).thenReturn(false); + when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(false); securityIndexSearcherWrapper = - new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState); + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService); DirectoryReader reader = securityIndexSearcherWrapper.wrap(esIn); assertThat(reader, sameInstance(esIn)); } public void testWrapSearcherWhenFeatureDisabled() throws Exception { securityIndexSearcherWrapper = - new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState); + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService); IndexSearcher indexSearcher = new IndexSearcher(esIn); IndexSearcher result = securityIndexSearcherWrapper.wrap(indexSearcher); assertThat(result, sameInstance(indexSearcher)); @@ -275,7 +293,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { DirectoryReader directoryReader = DocumentSubsetReader.wrap(esIn, bitsetFilterCache, new MatchAllDocsQuery()); IndexSearcher indexSearcher = new IndexSearcher(directoryReader); securityIndexSearcherWrapper = - new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState); + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService); IndexSearcher result = securityIndexSearcherWrapper.wrap(indexSearcher); assertThat(result, not(sameInstance(indexSearcher))); assertThat(result.getSimilarity(true), sameInstance(indexSearcher.getSimilarity(true))); @@ -284,7 +302,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { public void testIntersectScorerAndRoleBits() throws Exception { securityIndexSearcherWrapper = - new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState); + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService); final Directory directory = newDirectory(); IndexWriter iw = new IndexWriter( directory, @@ -373,7 +391,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { private void assertResolvedFields(String expression, String... expectedFields) { securityIndexSearcherWrapper = - new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState) { + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService) { @Override protected IndicesAccessControl getIndicesAccessControl() { IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, @@ -407,6 +425,60 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { doTestIndexSearcherWrapper(false, true); } + public void testTemplating() throws Exception { + User user = new User("_username", new String[]{"role1", "role2"}, "_full_name", "_email", + Collections.singletonMap("key", "value")); + securityIndexSearcherWrapper = + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, scriptService) { + + @Override + protected User getUser() { + return user; + } + }; + + ExecutableScript executableScript = mock(ExecutableScript.class); + when(scriptService.executable(any(Script.class), eq(ScriptContext.Standard.SEARCH), eq(Collections.emptyMap()))) + .thenReturn(executableScript); + + XContentBuilder builder = jsonBuilder(); + String query = new TermQueryBuilder("field", "{{_user.username}}").toXContent(builder, ToXContent.EMPTY_PARAMS).string(); + Script script = new Script(query, ScriptService.ScriptType.INLINE, null, Collections.singletonMap("custom", "value")); + builder = jsonBuilder().startObject().field("template"); + script.toXContent(builder, ToXContent.EMPTY_PARAMS); + BytesReference querySource = builder.endObject().bytes(); + + securityIndexSearcherWrapper.evaluateTemplate(querySource); + ArgumentCaptor