From 91441bbd2a2696e742ec3566c8919fda1368b9f8 Mon Sep 17 00:00:00 2001 From: Ryan Ernst Date: Sat, 16 Jul 2016 00:10:17 -0700 Subject: [PATCH 1/9] Internal: Remove script service proxy ScriptServiceProxy is a thin wrapper around the ScriptService which does a runAs the xpack user when compiling. But script services know nothing about xpack users, so this has no real effect. I believe this is a remnant of when we had indexed scripts, where the compilation may have done a get on the scripts index. This change removes the ScriptServiceProxy. It also renames Script in watcher to WatcherScript, to remove confusion between elasticsearch's Script and watchers Script. Original commit: elastic/x-pack-elasticsearch@4e2fdbc518a90d84210b50458ad02e09b79fc7e1 --- .../messy/tests/GroovyManualExecutionIT.java | 6 +- .../messy/tests/GroovyScriptConditionIT.java | 12 ++-- .../messy/tests/MessyTestUtils.java | 14 ++-- .../messy/tests/ScriptConditionSearchIT.java | 12 ++-- .../messy/tests/ScriptConditionTests.java | 36 +++++----- .../messy/tests/TransformIT.java | 14 ++-- .../messy/tests/SearchInputIT.java | 20 +++--- .../messy/tests/SearchTransformIT.java | 21 +++--- .../smoketest/WatcherTemplateTests.java | 12 +--- .../org/elasticsearch/xpack/XPackPlugin.java | 5 +- .../xpack/common/ScriptServiceProxy.java | 68 ------------------- .../text/DefaultTextTemplateEngine.java | 9 +-- .../xpack/common/text/TextTemplateModule.java | 2 - .../xpack/common/text/TextTemplateTests.java | 24 ++++--- .../watcher/condition/ConditionBuilders.java | 8 +-- .../script/ExecutableScriptCondition.java | 13 ++-- .../condition/script/ScriptCondition.java | 16 ++--- .../script/ScriptConditionFactory.java | 6 +- .../input/search/SearchInputFactory.java | 6 +- .../{Script.java => WatcherScript.java} | 46 ++++++++----- .../search/WatcherSearchTemplateRequest.java | 12 ++-- .../search/WatcherSearchTemplateService.java | 35 +++++----- .../watcher/transform/TransformBuilders.java | 8 +-- .../script/ExecutableScriptTransform.java | 15 ++-- .../transform/script/ScriptTransform.java | 16 ++--- .../script/ScriptTransformFactory.java | 6 +- .../search/SearchTransformFactory.java | 6 +- .../script/SleepScriptEngine.java | 5 +- .../watcher/support/WatcherUtilsTests.java | 10 +-- .../AbstractWatcherIntegrationTestCase.java | 6 +- .../xpack/watcher/test/WatcherTestUtils.java | 17 ++--- .../test/integration/BasicWatcherTests.java | 6 +- .../transform/TransformIntegrationTests.java | 14 ++-- .../script/ScriptTransformTests.java | 45 ++++++------ .../xpack/watcher/watch/WatchTests.java | 17 ++--- 35 files changed, 254 insertions(+), 314 deletions(-) delete mode 100644 elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/ScriptServiceProxy.java rename elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/{Script.java => WatcherScript.java} (85%) 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/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/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/x-pack/src/main/java/org/elasticsearch/xpack/XPackPlugin.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackPlugin.java index 63322c8aba3..fd9d2e3b290 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackPlugin.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackPlugin.java @@ -36,7 +36,6 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.index.IndexModule; import org.elasticsearch.license.plugin.Licensing; -import org.elasticsearch.license.plugin.core.LicensesService; import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.ScriptPlugin; @@ -49,7 +48,6 @@ import org.elasticsearch.xpack.action.TransportXPackInfoAction; import org.elasticsearch.xpack.action.TransportXPackUsageAction; import org.elasticsearch.xpack.action.XPackInfoAction; import org.elasticsearch.xpack.action.XPackUsageAction; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.common.http.HttpClient; import org.elasticsearch.xpack.common.http.HttpRequestTemplate; import org.elasticsearch.xpack.common.http.auth.HttpAuthFactory; @@ -75,6 +73,7 @@ import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.support.clock.Clock; import org.elasticsearch.xpack.support.clock.SystemClock; import org.elasticsearch.xpack.watcher.Watcher; +import org.elasticsearch.xpack.watcher.support.WatcherScript; public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin { @@ -231,7 +230,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin { @Override public ScriptContext.Plugin getCustomScriptContexts() { - return ScriptServiceProxy.INSTANCE; + return WatcherScript.CTX_PLUGIN; } @Override diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/ScriptServiceProxy.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/ScriptServiceProxy.java deleted file mode 100644 index 8f224cd5508..00000000000 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/ScriptServiceProxy.java +++ /dev/null @@ -1,68 +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.common; - -import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.script.CompiledScript; -import org.elasticsearch.script.ExecutableScript; -import org.elasticsearch.script.ScriptContext; -import org.elasticsearch.script.ScriptService; -import org.elasticsearch.xpack.security.SecurityContext; -import org.elasticsearch.xpack.security.user.XPackUser; -import org.elasticsearch.xpack.watcher.support.Script; - -import java.util.Map; - -import static java.util.Collections.emptyMap; - -/** - * Wraps {@link ScriptService} but ensure that all scripts are run or compiled as {@link XPackUser}. - */ -public class ScriptServiceProxy { - - private final ScriptService service; - private final SecurityContext securityContext; - - @Inject - public ScriptServiceProxy(ScriptService service, SecurityContext securityContext) { - this.service = service; - this.securityContext = securityContext; - } - - public CompiledScript compile(Script script) { - return compile(new org.elasticsearch.script.Script(script.script(), script.type(), script.lang(), script.params()), emptyMap()); - } - - public CompiledScript compile(org.elasticsearch.script.Script script, Map compileParams) { - return securityContext.executeAs(XPackUser.INSTANCE, () -> - service.compile(script, WatcherScriptContext.CTX, compileParams)); - } - - public ExecutableScript executable(CompiledScript compiledScript, Map vars) { - return securityContext.executeAs(XPackUser.INSTANCE, () -> - service.executable(compiledScript, vars)); - } - - public static final ScriptContext.Plugin INSTANCE = new ScriptContext.Plugin("xpack", "watch"); - - private static class WatcherScriptContext implements ScriptContext { - - public static final ScriptContext CTX = new WatcherScriptContext(); - - @Override - public String getKey() { - return INSTANCE.getKey(); - } - } - - /** - * Factory helper method for testing. - */ - public static ScriptServiceProxy of(ScriptService service) { - return new ScriptServiceProxy(service, SecurityContext.Insecure.INSTANCE); - } -} diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java index a9acedd2938..9fbc7596e1c 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java @@ -12,8 +12,9 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.Template; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import java.util.Collections; import java.util.HashMap; @@ -21,10 +22,10 @@ import java.util.Map; public class DefaultTextTemplateEngine extends AbstractComponent implements TextTemplateEngine { - private final ScriptServiceProxy service; + private final ScriptService service; @Inject - public DefaultTextTemplateEngine(Settings settings, ScriptServiceProxy service) { + public DefaultTextTemplateEngine(Settings settings, ScriptService service) { super(settings); this.service = service; } @@ -39,7 +40,7 @@ public class DefaultTextTemplateEngine extends AbstractComponent implements Text Map compileParams = compileParams(contentType); template = trimContentType(template); - CompiledScript compiledScript = service.compile(convert(template, model), compileParams); + CompiledScript compiledScript = service.compile(convert(template, model), WatcherScript.CTX, compileParams); ExecutableScript executable = service.executable(compiledScript, model); Object result = executable.run(); if (result instanceof BytesReference) { diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/TextTemplateModule.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/TextTemplateModule.java index bca84edc623..bbf3a811cda 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/TextTemplateModule.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/TextTemplateModule.java @@ -6,7 +6,6 @@ package org.elasticsearch.xpack.common.text; import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.xpack.common.ScriptServiceProxy; /** * @@ -15,7 +14,6 @@ public class TextTemplateModule extends AbstractModule { @Override protected void configure() { - bind(ScriptServiceProxy.class).asEagerSingleton(); bind(DefaultTextTemplateEngine.class).asEagerSingleton(); bind(TextTemplateEngine.class).to(DefaultTextTemplateEngine.class); } diff --git a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java index cc2199b79a5..8daa8b25917 100644 --- a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java +++ b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java @@ -14,10 +14,11 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService.ScriptType; import org.elasticsearch.script.Template; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.junit.Before; import java.util.Collections; @@ -37,16 +38,16 @@ import static org.mockito.Mockito.when; public class TextTemplateTests extends ESTestCase { - private ScriptServiceProxy proxy; + private ScriptService service; private TextTemplateEngine engine; private ExecutableScript script; private final String lang = "mustache"; @Before public void init() throws Exception { - proxy = mock(ScriptServiceProxy.class); + service = mock(ScriptService.class); script = mock(ExecutableScript.class); - engine = new DefaultTextTemplateEngine(Settings.EMPTY, proxy); + engine = new DefaultTextTemplateEngine(Settings.EMPTY, service); } public void testRender() throws Exception { @@ -59,9 +60,10 @@ public class TextTemplateTests extends ESTestCase { ScriptType type = randomFrom(ScriptType.values()); CompiledScript compiledScript = mock(CompiledScript.class); - when(proxy.compile(new Template(templateText, type, lang, null, merged), Collections.singletonMap("content_type", "text/plain"))) + when(service.compile(new Template(templateText, type, lang, null, merged), WatcherScript.CTX, + Collections.singletonMap("content_type", "text/plain"))) .thenReturn(compiledScript); - when(proxy.executable(compiledScript, model)).thenReturn(script); + when(service.executable(compiledScript, model)).thenReturn(script); when(script.run()).thenReturn("rendered_text"); TextTemplate template = templateBuilder(type, templateText).params(params).build(); @@ -75,10 +77,11 @@ public class TextTemplateTests extends ESTestCase { ScriptType scriptType = randomFrom(ScriptType.values()); CompiledScript compiledScript = mock(CompiledScript.class); - when(proxy.compile(new Template(templateText, scriptType, lang, null, model), + when(service.compile(new Template(templateText, scriptType, lang, null, model), + WatcherScript.CTX, Collections.singletonMap("content_type", "text/plain"))) .thenReturn(compiledScript); - when(proxy.executable(compiledScript, model)).thenReturn(script); + when(service.executable(compiledScript, model)).thenReturn(script); when(script.run()).thenReturn("rendered_text"); TextTemplate template = templateBuilder(scriptType, templateText).params(params).build(); @@ -90,10 +93,11 @@ public class TextTemplateTests extends ESTestCase { Map model = singletonMap("key", "model_val"); CompiledScript compiledScript = mock(CompiledScript.class); - when(proxy.compile(new Template(templateText, ScriptType.INLINE, lang, null, model), + when(service.compile(new Template(templateText, ScriptType.INLINE, lang, null, model), + WatcherScript.CTX, Collections.singletonMap("content_type", "text/plain"))) .thenReturn(compiledScript); - when(proxy.executable(compiledScript, model)).thenReturn(script); + when(service.executable(compiledScript, model)).thenReturn(script); when(script.run()).thenReturn("rendered_text"); TextTemplate template = new TextTemplate(templateText); diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/ConditionBuilders.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/ConditionBuilders.java index e21f50b0fd1..ae8c7bb05a3 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/ConditionBuilders.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/ConditionBuilders.java @@ -10,7 +10,7 @@ import org.elasticsearch.xpack.watcher.condition.compare.CompareCondition; import org.elasticsearch.xpack.watcher.condition.compare.array.ArrayCompareCondition; import org.elasticsearch.xpack.watcher.condition.never.NeverCondition; import org.elasticsearch.xpack.watcher.condition.script.ScriptCondition; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; /** * @@ -29,14 +29,14 @@ public final class ConditionBuilders { } public static ScriptCondition.Builder scriptCondition(String script) { - return scriptCondition(Script.inline(script)); + return scriptCondition(WatcherScript.inline(script)); } - public static ScriptCondition.Builder scriptCondition(Script.Builder script) { + public static ScriptCondition.Builder scriptCondition(WatcherScript.Builder script) { return scriptCondition(script.build()); } - public static ScriptCondition.Builder scriptCondition(Script script) { + public static ScriptCondition.Builder scriptCondition(WatcherScript script) { return ScriptCondition.builder(script); } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ExecutableScriptCondition.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ExecutableScriptCondition.java index c03ff54c65d..ab77559b778 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ExecutableScriptCondition.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ExecutableScriptCondition.java @@ -8,11 +8,14 @@ package org.elasticsearch.xpack.watcher.condition.script; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.xpack.watcher.condition.ExecutableCondition; import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; import org.elasticsearch.xpack.watcher.support.Variables; +import org.elasticsearch.xpack.watcher.support.WatcherScript; +import java.util.Collections; import java.util.Map; import static org.elasticsearch.xpack.watcher.support.Exceptions.invalidScript; @@ -22,14 +25,16 @@ import static org.elasticsearch.xpack.watcher.support.Exceptions.invalidScript; */ public class ExecutableScriptCondition extends ExecutableCondition { - private final ScriptServiceProxy scriptService; + private final ScriptService scriptService; private final CompiledScript compiledScript; - public ExecutableScriptCondition(ScriptCondition condition, ESLogger logger, ScriptServiceProxy scriptService) { + public ExecutableScriptCondition(ScriptCondition condition, ESLogger logger, ScriptService scriptService) { super(condition, logger); this.scriptService = scriptService; try { - compiledScript = scriptService.compile(condition.script); + Script script = new Script(condition.script.script(), condition.script.type(), + condition.script.lang(), condition.script.params()); + compiledScript = scriptService.compile(script, WatcherScript.CTX, Collections.emptyMap()); } catch (Exception e) { throw invalidScript("failed to compile script [{}] with lang [{}] of type [{}]", e, condition.script.script(), condition.script.lang(), condition.script.type(), e); diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptCondition.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptCondition.java index 85d4cf59fae..65bcd98c12b 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptCondition.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptCondition.java @@ -9,7 +9,7 @@ import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.xpack.watcher.condition.Condition; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import java.io.IOException; @@ -20,9 +20,9 @@ public class ScriptCondition implements Condition { public static final String TYPE = "script"; - final Script script; + final WatcherScript script; - public ScriptCondition(Script script) { + public ScriptCondition(WatcherScript script) { this.script = script; } @@ -31,7 +31,7 @@ public class ScriptCondition implements Condition { return TYPE; } - public Script getScript() { + public WatcherScript getScript() { return script; } @@ -57,7 +57,7 @@ public class ScriptCondition implements Condition { public static ScriptCondition parse(String watchId, XContentParser parser) throws IOException { try { - Script script = Script.parse(parser); + WatcherScript script = WatcherScript.parse(parser); return new ScriptCondition(script); } catch (ElasticsearchParseException pe) { throw new ElasticsearchParseException("could not parse [{}] condition for watch [{}]. failed to parse script", pe, TYPE, @@ -65,7 +65,7 @@ public class ScriptCondition implements Condition { } } - public static Builder builder(Script script) { + public static Builder builder(WatcherScript script) { return new Builder(script); } @@ -86,9 +86,9 @@ public class ScriptCondition implements Condition { public static class Builder implements Condition.Builder { - private final Script script; + private final WatcherScript script; - private Builder(Script script) { + private Builder(WatcherScript script) { this.script = script; } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptConditionFactory.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptConditionFactory.java index 4765954e3b4..0d575269157 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptConditionFactory.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/condition/script/ScriptConditionFactory.java @@ -9,8 +9,8 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.xpack.watcher.condition.ConditionFactory; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import java.io.IOException; @@ -19,10 +19,10 @@ import java.io.IOException; */ public class ScriptConditionFactory extends ConditionFactory { - private final ScriptServiceProxy scriptService; + private final ScriptService scriptService; @Inject - public ScriptConditionFactory(Settings settings, ScriptServiceProxy service) { + public ScriptConditionFactory(Settings settings, ScriptService service) { super(Loggers.getLogger(ExecutableScriptCondition.class, settings)); scriptService = service; } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/SearchInputFactory.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/SearchInputFactory.java index db111eb4f23..650bf376741 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/SearchInputFactory.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/search/SearchInputFactory.java @@ -13,9 +13,9 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.indices.query.IndicesQueriesRegistry; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.AggregatorParsers; import org.elasticsearch.search.suggest.Suggesters; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.watcher.input.InputFactory; import org.elasticsearch.xpack.watcher.input.simple.ExecutableSimpleInput; @@ -39,12 +39,12 @@ public class SearchInputFactory extends InputFactory params; - Script(String script) { + WatcherScript(String script) { this(script, null, null, null); } - Script(String script, @Nullable ScriptType type, @Nullable String lang, @Nullable Map params) { + WatcherScript(String script, @Nullable ScriptType type, @Nullable String lang, @Nullable Map params) { this.script = script; this.type = type; this.lang = lang; @@ -58,12 +62,16 @@ public class Script implements ToXContent { return params != null ? params : Collections.emptyMap(); } + public Script toScript() { + return new Script(script(), type(), lang(), params()); + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Script script1 = (Script) o; + WatcherScript script1 = (WatcherScript) o; if (!script.equals(script1.script)) return false; if (type != script1.type) return false; @@ -106,10 +114,10 @@ public class Script implements ToXContent { return builder.endObject(); } - public static Script parse(XContentParser parser) throws IOException { + public static WatcherScript parse(XContentParser parser) throws IOException { XContentParser.Token token = parser.currentToken(); if (token == XContentParser.Token.VALUE_STRING) { - return new Script(parser.text()); + return new WatcherScript(parser.text()); } if (token != XContentParser.Token.START_OBJECT) { throw new ElasticsearchParseException("expected a string value or an object, but found [{}] instead", token); @@ -170,7 +178,7 @@ public class Script implements ToXContent { Field.INLINE.getPreferredName(), Field.FILE.getPreferredName(), Field.ID.getPreferredName()); } assert type != null : "if script is not null, type should definitely not be null"; - return new Script(script, type, lang, params); + return new WatcherScript(script, type, lang, params); } public static Builder.Inline inline(String script) { @@ -211,7 +219,7 @@ public class Script implements ToXContent { return (B) this; } - public abstract Script build(); + public abstract WatcherScript build(); public static class Inline extends Builder { @@ -220,8 +228,8 @@ public class Script implements ToXContent { } @Override - public Script build() { - return new Script(script, type, lang, params); + public WatcherScript build() { + return new WatcherScript(script, type, lang, params); } } @@ -232,8 +240,8 @@ public class Script implements ToXContent { } @Override - public Script build() { - return new Script(script, type, lang, params); + public WatcherScript build() { + return new WatcherScript(script, type, lang, params); } } @@ -244,8 +252,8 @@ public class Script implements ToXContent { } @Override - public Script build() { - return new Script(script, type, lang, params); + public WatcherScript build() { + return new WatcherScript(script, type, lang, params); } } @@ -256,8 +264,8 @@ public class Script implements ToXContent { } @Override - public Script build() { - return new Script(script, type, lang, params); + public WatcherScript build() { + return new WatcherScript(script, type, lang, params); } } } @@ -271,4 +279,10 @@ public class Script implements ToXContent { } + private static class WatcherScriptContext implements ScriptContext { + @Override + public String getKey() { + return CTX_PLUGIN.getKey(); + } + } } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateRequest.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateRequest.java index cdccd47c635..7cb41f03b56 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateRequest.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateRequest.java @@ -20,7 +20,7 @@ import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.search.aggregations.AggregatorParsers; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.suggest.Suggesters; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.support.SearchRequestEquivalence; import java.io.IOException; @@ -36,9 +36,9 @@ import java.util.Objects; public class WatcherSearchTemplateRequest implements ToXContent { private final SearchRequest request; - @Nullable private final Script template; + @Nullable private final WatcherScript template; - public WatcherSearchTemplateRequest(SearchRequest searchRequest, @Nullable Script template) { + public WatcherSearchTemplateRequest(SearchRequest searchRequest, @Nullable WatcherScript template) { this.request = Objects.requireNonNull(searchRequest); this.template = template; } @@ -51,7 +51,7 @@ public class WatcherSearchTemplateRequest implements ToXContent { return request; } - public Script getTemplate() { + public WatcherScript getTemplate() { return template; } @@ -105,7 +105,7 @@ public class WatcherSearchTemplateRequest implements ToXContent { throws IOException { IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS; SearchRequest searchRequest = new SearchRequest(); - Script template = null; + WatcherScript template = null; XContentParser.Token token; String currentFieldName = null; @@ -190,7 +190,7 @@ public class WatcherSearchTemplateRequest implements ToXContent { indicesOptions = IndicesOptions.fromOptions(ignoreUnavailable, allowNoIndices, expandOpen, expandClosed, DEFAULT_INDICES_OPTIONS); } else if (ParseFieldMatcher.STRICT.match(currentFieldName, TEMPLATE_FIELD)) { - template = Script.parse(parser); + template = WatcherScript.parse(parser); } else { throw new ElasticsearchParseException("could not read search request. unexpected object field [" + currentFieldName + "]"); diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateService.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateService.java index ed00ea90dbd..6ab835e211e 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateService.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/support/search/WatcherSearchTemplateService.java @@ -17,17 +17,18 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.indices.query.IndicesQueriesRegistry; +import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.AggregatorParsers; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.suggest.Suggesters; -import org.elasticsearch.xpack.common.ScriptServiceProxy; 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.support.Variables; import org.elasticsearch.xpack.watcher.watch.Payload; import java.io.IOException; +import java.util.Collections; import java.util.Map; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; @@ -39,17 +40,17 @@ public class WatcherSearchTemplateService extends AbstractComponent { private static final String DEFAULT_LANG = "mustache"; - private final ScriptServiceProxy scriptService; + private final ScriptService scriptService; private final ParseFieldMatcher parseFieldMatcher; private final IndicesQueriesRegistry queryRegistry; private final AggregatorParsers aggsParsers; private final Suggesters suggesters; @Inject - public WatcherSearchTemplateService(Settings settings, ScriptServiceProxy scriptServiceProxy, + public WatcherSearchTemplateService(Settings settings, ScriptService scriptService, IndicesQueriesRegistry queryRegistry, AggregatorParsers aggregatorParsers, Suggesters suggesters) { super(settings); - this.scriptService = scriptServiceProxy; + this.scriptService = scriptService; this.queryRegistry = queryRegistry; this.aggsParsers = aggregatorParsers; this.suggesters = suggesters; @@ -65,7 +66,7 @@ public class WatcherSearchTemplateService extends AbstractComponent { .indices(prototype.getRequest().indices()) .types(prototype.getRequest().types()); - Script template = null; + WatcherScript template = null; // Due the inconsistency with templates in ES 1.x, we maintain our own template format. // This template format we use now, will become the template structure in ES 2.0 @@ -76,26 +77,26 @@ public class WatcherSearchTemplateService extends AbstractComponent { if (prototype.getRequest().source() != null) { try (XContentBuilder builder = jsonBuilder()) { prototype.getRequest().source().toXContent(builder, ToXContent.EMPTY_PARAMS); - template = Script.inline(builder.string()).lang(DEFAULT_LANG).params(watcherContextParams).build(); + template = WatcherScript.inline(builder.string()).lang(DEFAULT_LANG).params(watcherContextParams).build(); } } else if (prototype.getTemplate() != null) { // Here we convert watcher template into a ES core templates. Due to the different format we use, we // convert to the template format used in ES core - Script templatePrototype = prototype.getTemplate(); + WatcherScript templatePrototype = prototype.getTemplate(); if (templatePrototype.params() != null) { watcherContextParams.putAll(templatePrototype.params()); } - Script.Builder builder; + WatcherScript.Builder builder; if (templatePrototype.type() == ScriptService.ScriptType.INLINE) { - builder = Script.inline(templatePrototype.script()); + builder = WatcherScript.inline(templatePrototype.script()); } else if (templatePrototype.type() == ScriptService.ScriptType.FILE) { - builder = Script.file(templatePrototype.script()); + builder = WatcherScript.file(templatePrototype.script()); } else if (templatePrototype.type() == ScriptService.ScriptType.STORED) { - builder = Script.indexed(templatePrototype.script()); + builder = WatcherScript.indexed(templatePrototype.script()); } else { - builder = Script.defaultType(templatePrototype.script()); + builder = WatcherScript.defaultType(templatePrototype.script()); } template = builder.lang(templatePrototype.lang()).params(watcherContextParams).build(); } @@ -105,16 +106,16 @@ public class WatcherSearchTemplateService extends AbstractComponent { } /** - * Converts a {@link Script} to a {@link org.elasticsearch.search.builder.SearchSourceBuilder} + * Converts a {@link WatcherScript} to a {@link org.elasticsearch.search.builder.SearchSourceBuilder} */ - private SearchSourceBuilder convert(Script template) throws IOException { + private SearchSourceBuilder convert(WatcherScript template) throws IOException { SearchSourceBuilder sourceBuilder = SearchSourceBuilder.searchSource(); if (template == null) { // falling back to an empty body return sourceBuilder; } - - BytesReference source = (BytesReference) scriptService.executable(scriptService.compile(template), template.params()).run(); + CompiledScript compiledScript = scriptService.compile(template.toScript(), WatcherScript.CTX, Collections.emptyMap()); + BytesReference source = (BytesReference) scriptService.executable(compiledScript, template.params()).run(); if (source != null && source.length() > 0) { try (XContentParser parser = XContentFactory.xContent(source).createParser(source)) { sourceBuilder.parseXContent(new QueryParseContext(queryRegistry, parser, parseFieldMatcher), aggsParsers, suggesters); diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/TransformBuilders.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/TransformBuilders.java index b39e3b9655d..505fc781d52 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/TransformBuilders.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/TransformBuilders.java @@ -6,7 +6,7 @@ package org.elasticsearch.xpack.watcher.transform; import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateRequest; import org.elasticsearch.xpack.watcher.transform.chain.ChainTransform; import org.elasticsearch.xpack.watcher.transform.script.ScriptTransform; @@ -29,14 +29,14 @@ public final class TransformBuilders { } public static ScriptTransform.Builder scriptTransform(String script) { - return scriptTransform(Script.inline(script)); + return scriptTransform(WatcherScript.inline(script)); } - public static ScriptTransform.Builder scriptTransform(Script.Builder script) { + public static ScriptTransform.Builder scriptTransform(WatcherScript.Builder script) { return scriptTransform(script.build()); } - public static ScriptTransform.Builder scriptTransform(Script script) { + public static ScriptTransform.Builder scriptTransform(WatcherScript script) { return ScriptTransform.builder(script); } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ExecutableScriptTransform.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ExecutableScriptTransform.java index 11a289e0560..67a2a2881ea 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ExecutableScriptTransform.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ExecutableScriptTransform.java @@ -8,13 +8,14 @@ package org.elasticsearch.xpack.watcher.transform.script; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; +import org.elasticsearch.script.ScriptService; 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.transform.ExecutableTransform; import org.elasticsearch.xpack.watcher.watch.Payload; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -26,15 +27,15 @@ import static org.elasticsearch.xpack.watcher.support.Variables.createCtxModel; */ public class ExecutableScriptTransform extends ExecutableTransform { - private final ScriptServiceProxy scriptService; + private final ScriptService scriptService; private final CompiledScript compiledScript; - public ExecutableScriptTransform(ScriptTransform transform, ESLogger logger, ScriptServiceProxy scriptService) { + public ExecutableScriptTransform(ScriptTransform transform, ESLogger logger, ScriptService scriptService) { super(transform, logger); this.scriptService = scriptService; - Script script = transform.getScript(); + WatcherScript script = transform.getScript(); try { - compiledScript = scriptService.compile(script); + compiledScript = scriptService.compile(script.toScript(), WatcherScript.CTX, Collections.emptyMap()); } catch (Exception e) { throw invalidScript("failed to compile script [{}] with lang [{}] of type [{}]", e, script.script(), script.lang(), script.type(), e); @@ -53,7 +54,7 @@ public class ExecutableScriptTransform extends ExecutableTransform model = new HashMap<>(); model.putAll(script.params()); model.putAll(createCtxModel(ctx, payload)); diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransform.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransform.java index 49811d00619..5c4b2f6898c 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransform.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransform.java @@ -8,7 +8,7 @@ package org.elasticsearch.xpack.watcher.transform.script; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.transform.Transform; import org.elasticsearch.xpack.watcher.watch.Payload; @@ -21,9 +21,9 @@ public class ScriptTransform implements Transform { public static final String TYPE = "script"; - private final Script script; + private final WatcherScript script; - public ScriptTransform(Script script) { + public ScriptTransform(WatcherScript script) { this.script = script; } @@ -32,7 +32,7 @@ public class ScriptTransform implements Transform { return TYPE; } - public Script getScript() { + public WatcherScript getScript() { return script; } @@ -58,7 +58,7 @@ public class ScriptTransform implements Transform { public static ScriptTransform parse(String watchId, XContentParser parser) throws IOException { try { - Script script = Script.parse(parser); + WatcherScript script = WatcherScript.parse(parser); return new ScriptTransform(script); } catch (ElasticsearchParseException pe) { throw new ElasticsearchParseException("could not parse [{}] transform for watch [{}]. failed to parse script", pe, TYPE, @@ -66,7 +66,7 @@ public class ScriptTransform implements Transform { } } - public static Builder builder(Script script) { + public static Builder builder(WatcherScript script) { return new Builder(script); } @@ -88,9 +88,9 @@ public class ScriptTransform implements Transform { public static class Builder implements Transform.Builder { - private final Script script; + private final WatcherScript script; - public Builder(Script script) { + public Builder(WatcherScript script) { this.script = script; } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformFactory.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformFactory.java index 2928f6b8012..e00e6fec40b 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformFactory.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformFactory.java @@ -9,7 +9,7 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.xpack.watcher.transform.TransformFactory; import java.io.IOException; @@ -19,10 +19,10 @@ import java.io.IOException; */ public class ScriptTransformFactory extends TransformFactory { - private final ScriptServiceProxy scriptService; + private final ScriptService scriptService; @Inject - public ScriptTransformFactory(Settings settings, ScriptServiceProxy scriptService) { + public ScriptTransformFactory(Settings settings, ScriptService scriptService) { super(Loggers.getLogger(ExecutableScriptTransform.class, settings)); this.scriptService = scriptService; } diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java index 28d9e0aa599..c510c96e4c6 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/transform/search/SearchTransformFactory.java @@ -15,10 +15,10 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.query.QueryParseContext; import org.elasticsearch.indices.query.IndicesQueriesRegistry; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.aggregations.AggregatorParsers; import org.elasticsearch.search.suggest.Suggesters; import org.elasticsearch.xpack.security.InternalClient; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.watcher.support.init.proxy.WatcherClientProxy; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateService; import org.elasticsearch.xpack.watcher.transform.TransformFactory; @@ -38,11 +38,11 @@ public class SearchTransformFactory extends TransformFactory params = new HashMap<>(); if (randomBoolean()) { @@ -209,7 +210,8 @@ public class WatcherUtilsTests extends ESTestCase { } } String text = randomAsciiOfLengthBetween(1, 5); - template = randomFrom(Script.inline(text), Script.file(text), Script.indexed(text)) .params(params).build(); + template = randomFrom(WatcherScript.inline(text), WatcherScript.file(text), WatcherScript.indexed(text)) + .params(params).build(); builder.field("template", template); } builder.endObject(); diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java index af254eb1a09..e0b387ab721 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/AbstractWatcherIntegrationTestCase.java @@ -22,6 +22,7 @@ import org.elasticsearch.common.util.Callback; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.xpack.monitoring.Monitoring; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.script.MockMustacheScriptEngine; @@ -51,7 +52,6 @@ import org.elasticsearch.xpack.watcher.WatcherLicensee; import org.elasticsearch.xpack.watcher.support.WatcherIndexTemplateRegistry; import org.elasticsearch.xpack.support.clock.ClockMock; import org.elasticsearch.xpack.common.http.HttpClient; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.trigger.ScheduleTriggerEngineMock; import org.elasticsearch.xpack.watcher.trigger.TriggerService; @@ -356,8 +356,8 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase return randomBoolean() ? new XPackClient(client).watcher() : new WatcherClient(client); } - protected ScriptServiceProxy scriptService() { - return internalCluster().getInstance(ScriptServiceProxy.class); + protected ScriptService scriptService() { + return internalCluster().getInstance(ScriptService.class); } protected HttpClient watcherHttpClient() { diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/WatcherTestUtils.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/WatcherTestUtils.java index dd85faa8f06..83aed79d10e 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/WatcherTestUtils.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/WatcherTestUtils.java @@ -8,11 +8,7 @@ package org.elasticsearch.xpack.watcher.test; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; @@ -20,7 +16,6 @@ import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.env.Environment; import org.elasticsearch.script.ScriptContextRegistry; @@ -31,7 +26,6 @@ import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.common.http.HttpClient; import org.elasticsearch.xpack.common.http.HttpMethod; import org.elasticsearch.xpack.common.http.HttpRequestTemplate; @@ -56,6 +50,7 @@ import org.elasticsearch.xpack.watcher.execution.Wid; import org.elasticsearch.xpack.watcher.input.search.ExecutableSearchInput; import org.elasticsearch.xpack.watcher.input.simple.ExecutableSimpleInput; import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; +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; @@ -71,11 +66,9 @@ import org.elasticsearch.xpack.watcher.watch.Watch; import org.elasticsearch.xpack.watcher.watch.WatchStatus; import org.hamcrest.Matcher; import org.joda.time.DateTime; -import org.mockito.Mockito; import javax.mail.internet.AddressException; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -245,19 +238,19 @@ public final class WatcherTestUtils { new WatchStatus(now, statuses)); } - 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") .put("path.home", createTempDir()) .build(); - ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Collections.singletonList(ScriptServiceProxy.INSTANCE)); + ScriptContextRegistry scriptContextRegistry = new ScriptContextRegistry(Collections.singletonList(WatcherScript.CTX_PLUGIN)); ScriptEngineRegistry scriptEngineRegistry = new ScriptEngineRegistry(Collections.emptyList()); 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); } public static SearchType getRandomSupportedSearchType() { diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java index 5a1cb2d465f..54bb31fd61c 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/test/integration/BasicWatcherTests.java @@ -15,7 +15,7 @@ import org.elasticsearch.xpack.support.clock.SystemClock; import org.elasticsearch.xpack.watcher.client.WatchSourceBuilder; import org.elasticsearch.xpack.watcher.client.WatcherClient; import org.elasticsearch.xpack.watcher.condition.compare.CompareCondition; -import org.elasticsearch.xpack.watcher.support.Script; +import org.elasticsearch.xpack.watcher.support.WatcherScript; import org.elasticsearch.xpack.watcher.support.search.WatcherSearchTemplateRequest; import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource; import org.elasticsearch.xpack.watcher.test.AbstractWatcherIntegrationTestCase; @@ -259,7 +259,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { .setSource(jsonBuilder().startObject().field("template").value(searchSourceBuilder).endObject().bytes()) .get()); - Script template = Script.indexed("my-template").lang("mustache").build(); + WatcherScript template = WatcherScript.indexed("my-template").lang("mustache").build(); SearchRequest searchRequest = newInputSearchRequest("events"); testConditionSearch(searchRequest, template); } @@ -368,7 +368,7 @@ public class BasicWatcherTests extends AbstractWatcherIntegrationTestCase { } } - private void testConditionSearch(SearchRequest request, Script template) throws Exception { + private void testConditionSearch(SearchRequest request, WatcherScript template) throws Exception { // reset, so we don't miss event docs when we filter over the _timestamp field. timeWarp().clock().setTime(SystemClock.INSTANCE.nowUTC()); diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/TransformIntegrationTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/TransformIntegrationTests.java index 989c17e4a25..2062d822514 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/TransformIntegrationTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/TransformIntegrationTests.java @@ -12,7 +12,7 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.io.Streams; import org.elasticsearch.common.settings.Settings; -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; @@ -63,10 +63,10 @@ public class TransformIntegrationTests extends AbstractWatcherIntegrationTestCas } 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() @@ -74,10 +74,10 @@ public class TransformIntegrationTests extends AbstractWatcherIntegrationTestCas .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: @@ -173,8 +173,8 @@ public class TransformIntegrationTests extends AbstractWatcherIntegrationTestCas } 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/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformTests.java index 6e971535e16..d0788764bfe 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/transform/script/ScriptTransformTests.java @@ -13,14 +13,14 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.GeneralScriptException; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.ScriptService.ScriptType; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; 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.support.Variables; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.watcher.transform.Transform; import org.elasticsearch.xpack.watcher.watch.Payload; import org.junit.After; @@ -35,7 +35,7 @@ import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xpack.watcher.support.Exceptions.illegalArgument; import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.EMPTY_PAYLOAD; -import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.getScriptServiceProxy; +import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.createScriptService; import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.mockExecutionContext; import static org.elasticsearch.xpack.watcher.test.WatcherTestUtils.simplePayload; import static org.hamcrest.Matchers.containsString; @@ -63,12 +63,12 @@ public class ScriptTransformTests extends ESTestCase { } public void testExecute_MapValue() throws Exception { - ScriptServiceProxy service = mock(ScriptServiceProxy.class); + ScriptService service = mock(ScriptService.class); ScriptType type = randomFrom(ScriptType.values()); Map params = Collections.emptyMap(); - Script script = scriptBuilder(type, "_script").lang("_lang").params(params).build(); + WatcherScript script = scriptBuilder(type, "_script").lang("_lang").params(params).build(); CompiledScript compiledScript = mock(CompiledScript.class); - when(service.compile(script)).thenReturn(compiledScript); + when(service.compile(script.toScript(), WatcherScript.CTX, Collections.emptyMap())).thenReturn(compiledScript); ExecutableScriptTransform transform = new ExecutableScriptTransform(new ScriptTransform(script), logger, service); WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD); @@ -91,12 +91,12 @@ public class ScriptTransformTests extends ESTestCase { } public void testExecuteMapValueFailure() throws Exception { - ScriptServiceProxy service = mock(ScriptServiceProxy.class); + ScriptService service = mock(ScriptService.class); ScriptType type = randomFrom(ScriptType.values()); Map params = Collections.emptyMap(); - Script script = scriptBuilder(type, "_script").lang("_lang").params(params).build(); + WatcherScript script = scriptBuilder(type, "_script").lang("_lang").params(params).build(); CompiledScript compiledScript = mock(CompiledScript.class); - when(service.compile(script)).thenReturn(compiledScript); + when(service.compile(script.toScript(), WatcherScript.CTX, Collections.emptyMap())).thenReturn(compiledScript); ExecutableScriptTransform transform = new ExecutableScriptTransform(new ScriptTransform(script), logger, service); WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD); @@ -117,13 +117,12 @@ public class ScriptTransformTests extends ESTestCase { } public void testExecuteNonMapValue() throws Exception { - ScriptServiceProxy service = mock(ScriptServiceProxy.class); - + ScriptService service = mock(ScriptService.class); ScriptType type = randomFrom(ScriptType.values()); Map params = Collections.emptyMap(); - Script script = scriptBuilder(type, "_script").lang("_lang").params(params).build(); + WatcherScript script = scriptBuilder(type, "_script").lang("_lang").params(params).build(); CompiledScript compiledScript = mock(CompiledScript.class); - when(service.compile(script)).thenReturn(compiledScript); + when(service.compile(script.toScript(), WatcherScript.CTX, Collections.emptyMap())).thenReturn(compiledScript); ExecutableScriptTransform transform = new ExecutableScriptTransform(new ScriptTransform(script), logger, service); WatchExecutionContext ctx = mockExecutionContext("_name", EMPTY_PAYLOAD); @@ -145,7 +144,7 @@ public class ScriptTransformTests extends ESTestCase { } public void testParser() throws Exception { - ScriptServiceProxy service = mock(ScriptServiceProxy.class); + ScriptService service = mock(ScriptService.class); ScriptType type = randomFrom(ScriptType.values()); XContentBuilder builder = jsonBuilder().startObject(); builder.field(scriptTypeField(type), "_script"); @@ -156,22 +155,22 @@ public class ScriptTransformTests extends ESTestCase { XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); parser.nextToken(); ExecutableScriptTransform transform = new ScriptTransformFactory(Settings.EMPTY, service).parseExecutable("_id", parser); - Script script = scriptBuilder(type, "_script").lang("_lang").params(singletonMap("key", "value")).build(); + WatcherScript script = scriptBuilder(type, "_script").lang("_lang").params(singletonMap("key", "value")).build(); assertThat(transform.transform().getScript(), equalTo(script)); } public void testParserString() throws Exception { - ScriptServiceProxy service = mock(ScriptServiceProxy.class); + ScriptService service = mock(ScriptService.class); XContentBuilder builder = jsonBuilder().value("_script"); XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); parser.nextToken(); ExecutableScriptTransform transform = new ScriptTransformFactory(Settings.EMPTY, service).parseExecutable("_id", parser); - assertThat(transform.transform().getScript(), equalTo(Script.defaultType("_script").build())); + assertThat(transform.transform().getScript(), equalTo(WatcherScript.defaultType("_script").build())); } public void testScriptConditionParserBadScript() throws Exception { - ScriptTransformFactory transformFactory = new ScriptTransformFactory(Settings.builder().build(), getScriptServiceProxy(tp)); + ScriptTransformFactory transformFactory = new ScriptTransformFactory(Settings.builder().build(), createScriptService(tp)); ScriptType scriptType = randomFrom(ScriptType.values()); String script; switch (scriptType) { @@ -203,7 +202,7 @@ public class ScriptTransformTests extends ESTestCase { } public void testScriptConditionParserBadLang() throws Exception { - ScriptTransformFactory transformFactory = new ScriptTransformFactory(Settings.builder().build(), getScriptServiceProxy(tp)); + ScriptTransformFactory transformFactory = new ScriptTransformFactory(Settings.builder().build(), createScriptService(tp)); ScriptType scriptType = randomFrom(ScriptType.values()); String script = "return true"; XContentBuilder builder = jsonBuilder().startObject() @@ -224,11 +223,11 @@ public class ScriptTransformTests extends ESTestCase { } } - static Script.Builder scriptBuilder(ScriptType type, String script) { + static WatcherScript.Builder scriptBuilder(ScriptType type, String script) { switch (type) { - case INLINE: return Script.inline(script); - case FILE: return Script.file(script); - case STORED: return Script.indexed(script); + case INLINE: return WatcherScript.inline(script); + case FILE: return WatcherScript.file(script); + case STORED: return WatcherScript.indexed(script); default: throw illegalArgument("unsupported script type [{}]", type); } diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchTests.java index c867e1d43d5..b917def8c68 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/watch/WatchTests.java @@ -16,8 +16,8 @@ import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryParser; import org.elasticsearch.indices.query.IndicesQueriesRegistry; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.elasticsearch.xpack.common.http.HttpClient; import org.elasticsearch.xpack.common.http.HttpMethod; import org.elasticsearch.xpack.common.http.HttpRequestTemplate; @@ -78,7 +78,7 @@ 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.input.simple.SimpleInputFactory; -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; @@ -141,7 +141,7 @@ import static org.joda.time.DateTimeZone.UTC; import static org.mockito.Mockito.mock; public class WatchTests extends ESTestCase { - private ScriptServiceProxy scriptService; + private ScriptService scriptService; private WatcherClientProxy client; private HttpClient httpClient; private EmailService emailService; @@ -155,7 +155,7 @@ public class WatchTests extends ESTestCase { @Before public void init() throws Exception { - scriptService = mock(ScriptServiceProxy.class); + scriptService = mock(ScriptService.class); client = mock(WatcherClientProxy.class); httpClient = mock(HttpClient.class); emailService = mock(EmailService.class); @@ -363,7 +363,7 @@ public class WatchTests extends ESTestCase { String type = randomFrom(ScriptCondition.TYPE, AlwaysCondition.TYPE, CompareCondition.TYPE, ArrayCompareCondition.TYPE); switch (type) { case ScriptCondition.TYPE: - return new ExecutableScriptCondition(new ScriptCondition(Script.inline("_script").build()), logger, scriptService); + return new ExecutableScriptCondition(new ScriptCondition(WatcherScript.inline("_script").build()), logger, scriptService); case CompareCondition.TYPE: return new ExecutableCompareCondition(new CompareCondition("_path", randomFrom(Op.values()), randomFrom(5, "3")), logger, SystemClock.INSTANCE); @@ -400,7 +400,7 @@ public class WatchTests extends ESTestCase { DateTimeZone timeZone = randomBoolean() ? DateTimeZone.UTC : null; switch (type) { case ScriptTransform.TYPE: - return new ExecutableScriptTransform(new ScriptTransform(Script.inline("_script").build()), logger, scriptService); + return new ExecutableScriptTransform(new ScriptTransform(WatcherScript.inline("_script").build()), logger, scriptService); case SearchTransform.TYPE: SearchTransform transform = new SearchTransform( new WatcherSearchTemplateRequest(matchAllRequest(DEFAULT_INDICES_OPTIONS), null), timeout, timeZone); @@ -408,14 +408,15 @@ public class WatchTests extends ESTestCase { default: // chain SearchTransform searchTransform = new SearchTransform( new WatcherSearchTemplateRequest(matchAllRequest(DEFAULT_INDICES_OPTIONS), null), timeout, timeZone); - ScriptTransform scriptTransform = new ScriptTransform(Script.inline("_script").build()); + ScriptTransform scriptTransform = new ScriptTransform(WatcherScript.inline("_script").build()); ChainTransform chainTransform = new ChainTransform(Arrays.asList(searchTransform, scriptTransform)); return new ExecutableChainTransform(chainTransform, logger, Arrays.asList( new ExecutableSearchTransform(new SearchTransform( new WatcherSearchTemplateRequest(matchAllRequest(DEFAULT_INDICES_OPTIONS), null), timeout, timeZone), logger, client, searchTemplateService, null), - new ExecutableScriptTransform(new ScriptTransform(Script.inline("_script").build()), logger, scriptService))); + new ExecutableScriptTransform(new ScriptTransform(WatcherScript.inline("_script").build()), + logger, scriptService))); } } From 5b5e0bd787fa006f7baff83b04a39728cfe1c68e Mon Sep 17 00:00:00 2001 From: Martijn van Groningen Date: Thu, 14 Jul 2016 09:29:17 +0200 Subject: [PATCH 2/9] Updated xpack for changed in elastic/elasticsearch#19425 related to templates Original commit: elastic/x-pack-elasticsearch@7747f92b8969724e8d3cdc3f3ca5554b86889612 --- .../messy/tests/SecurityCachePermissionIT.java | 10 ++++------ .../xpack/common/text/DefaultTextTemplateEngine.java | 6 +++--- .../xpack/common/text/TextTemplateTests.java | 8 ++++---- 3 files changed, 11 insertions(+), 13 deletions(-) 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/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java index a9acedd2938..0d15732e7b9 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/common/text/DefaultTextTemplateEngine.java @@ -12,7 +12,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; -import org.elasticsearch.script.Template; +import org.elasticsearch.script.Script; import org.elasticsearch.xpack.common.ScriptServiceProxy; import java.util.Collections; @@ -79,11 +79,11 @@ public class DefaultTextTemplateEngine extends AbstractComponent implements Text return null; } - private Template convert(TextTemplate textTemplate, Map model) { + private Script convert(TextTemplate textTemplate, Map model) { Map mergedModel = new HashMap<>(); mergedModel.putAll(textTemplate.getParams()); mergedModel.putAll(model); - return new Template(textTemplate.getTemplate(), textTemplate.getType(), "mustache", textTemplate.getContentType(), mergedModel); + return new Script(textTemplate.getTemplate(), textTemplate.getType(), "mustache", mergedModel, textTemplate.getContentType()); } private Map compileParams(XContentType contentType) { diff --git a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java index cc2199b79a5..9df35bbad37 100644 --- a/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java +++ b/elasticsearch/x-pack/src/test/java/org/elasticsearch/xpack/common/text/TextTemplateTests.java @@ -15,7 +15,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.script.CompiledScript; import org.elasticsearch.script.ExecutableScript; import org.elasticsearch.script.ScriptService.ScriptType; -import org.elasticsearch.script.Template; +import org.elasticsearch.script.Script; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.common.ScriptServiceProxy; import org.junit.Before; @@ -59,7 +59,7 @@ public class TextTemplateTests extends ESTestCase { ScriptType type = randomFrom(ScriptType.values()); CompiledScript compiledScript = mock(CompiledScript.class); - when(proxy.compile(new Template(templateText, type, lang, null, merged), Collections.singletonMap("content_type", "text/plain"))) + when(proxy.compile(new Script(templateText, type, lang, merged), Collections.singletonMap("content_type", "text/plain"))) .thenReturn(compiledScript); when(proxy.executable(compiledScript, model)).thenReturn(script); when(script.run()).thenReturn("rendered_text"); @@ -75,7 +75,7 @@ public class TextTemplateTests extends ESTestCase { ScriptType scriptType = randomFrom(ScriptType.values()); CompiledScript compiledScript = mock(CompiledScript.class); - when(proxy.compile(new Template(templateText, scriptType, lang, null, model), + when(proxy.compile(new Script(templateText, scriptType, lang, model), Collections.singletonMap("content_type", "text/plain"))) .thenReturn(compiledScript); when(proxy.executable(compiledScript, model)).thenReturn(script); @@ -90,7 +90,7 @@ public class TextTemplateTests extends ESTestCase { Map model = singletonMap("key", "model_val"); CompiledScript compiledScript = mock(CompiledScript.class); - when(proxy.compile(new Template(templateText, ScriptType.INLINE, lang, null, model), + when(proxy.compile(new Script(templateText, ScriptType.INLINE, lang, model), Collections.singletonMap("content_type", "text/plain"))) .thenReturn(compiledScript); when(proxy.executable(compiledScript, model)).thenReturn(script); From c7e4f51d56f64cf82e55407e97d3cc61b5f9ad94 Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Mon, 18 Jul 2016 10:54:48 +0200 Subject: [PATCH 3/9] Watcher: Prioritize configured response content type in HttpInput (elastic/elasticsearch#2790) When a HTTP input has a configured response content, then this should always be treated as preferred over the content type that is returned by the server in order to give the user the power to decide. This also refactors the code a bit to make it more readable. Closes elastic/elasticsearch#2211 Original commit: elastic/x-pack-elasticsearch@ecdb4f931c98f313135fa12765715bb34ad0d474 --- .../input/http/ExecutableHttpInput.java | 59 ++++++++----------- .../watcher/input/http/HttpInputTests.java | 33 ++++++++--- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/http/ExecutableHttpInput.java b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/http/ExecutableHttpInput.java index e905fc2f4ee..c38dde7af28 100644 --- a/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/http/ExecutableHttpInput.java +++ b/elasticsearch/x-pack/watcher/src/main/java/org/elasticsearch/xpack/watcher/input/http/ExecutableHttpInput.java @@ -11,14 +11,14 @@ import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; -import org.elasticsearch.xpack.watcher.input.ExecutableInput; -import org.elasticsearch.xpack.watcher.support.Variables; -import org.elasticsearch.xpack.watcher.support.XContentFilterKeysUtils; import org.elasticsearch.xpack.common.http.HttpClient; import org.elasticsearch.xpack.common.http.HttpRequest; import org.elasticsearch.xpack.common.http.HttpResponse; import org.elasticsearch.xpack.common.text.TextTemplateEngine; +import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; +import org.elasticsearch.xpack.watcher.input.ExecutableInput; +import org.elasticsearch.xpack.watcher.support.Variables; +import org.elasticsearch.xpack.watcher.support.XContentFilterKeysUtils; import org.elasticsearch.xpack.watcher.watch.Payload; import java.util.HashMap; @@ -59,42 +59,35 @@ public class ExecutableHttpInput extends ExecutableInput payloadMap = new HashMap<>(); - if (input.getExtractKeys() != null) { - payloadMap.putAll(XContentFilterKeysUtils.filterMapOrdered(input.getExtractKeys(), parser)); - } else { - if (parser != null) { - payloadMap.putAll(parser.mapOrdered()); - } else { - payloadMap.put("_value", response.body().utf8ToString()); + if (contentType != null) { + try (XContentParser parser = contentType.xContent().createParser(response.body())) { + if (input.getExtractKeys() != null) { + payloadMap.putAll(XContentFilterKeysUtils.filterMapOrdered(input.getExtractKeys(), parser)); + } else { + payloadMap.putAll(parser.mapOrdered()); + } + } catch (Exception e) { + throw new ElasticsearchParseException("could not parse response body [{}] it does not appear to be [{}]", type(), ctx.id(), + response.body().utf8ToString(), contentType.shortName()); } + } else { + payloadMap.put("_value", response.body().utf8ToString()); } + if (headers.size() > 0) { payloadMap.put("_headers", headers); } diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputTests.java index 5fd52fa4c92..af4d64aa390 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/input/http/HttpInputTests.java @@ -14,13 +14,6 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.watcher.actions.ExecutableActions; -import org.elasticsearch.xpack.watcher.condition.always.ExecutableAlwaysCondition; -import org.elasticsearch.xpack.watcher.execution.TriggeredExecutionContext; -import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; -import org.elasticsearch.xpack.watcher.input.InputBuilders; -import org.elasticsearch.xpack.watcher.input.simple.ExecutableSimpleInput; -import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; import org.elasticsearch.xpack.common.http.HttpClient; import org.elasticsearch.xpack.common.http.HttpContentType; import org.elasticsearch.xpack.common.http.HttpMethod; @@ -34,6 +27,13 @@ import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth; import org.elasticsearch.xpack.common.http.auth.basic.BasicAuthFactory; import org.elasticsearch.xpack.common.text.TextTemplate; import org.elasticsearch.xpack.common.text.TextTemplateEngine; +import org.elasticsearch.xpack.watcher.actions.ExecutableActions; +import org.elasticsearch.xpack.watcher.condition.always.ExecutableAlwaysCondition; +import org.elasticsearch.xpack.watcher.execution.TriggeredExecutionContext; +import org.elasticsearch.xpack.watcher.execution.WatchExecutionContext; +import org.elasticsearch.xpack.watcher.input.InputBuilders; +import org.elasticsearch.xpack.watcher.input.simple.ExecutableSimpleInput; +import org.elasticsearch.xpack.watcher.input.simple.SimpleInput; import org.elasticsearch.xpack.watcher.trigger.schedule.IntervalSchedule; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTrigger; import org.elasticsearch.xpack.watcher.trigger.schedule.ScheduleTriggerEvent; @@ -58,6 +58,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.joda.time.DateTimeZone.UTC; import static org.mockito.Matchers.any; @@ -270,6 +271,24 @@ public class HttpInputTests extends ESTestCase { assertThat(result.payload().data().get("_headers"), equalTo(expectedHeaderMap)); } + public void testThatExpectedContentTypeOverridesReturnedContentType() throws Exception { + HttpRequestTemplate template = HttpRequestTemplate.builder("localhost", 9200).fromUrl("http:://127.0.0.1:12345").build(); + HttpInput httpInput = new HttpInput(template, HttpContentType.TEXT, null); + ExecutableHttpInput input = new ExecutableHttpInput(httpInput, logger, httpClient, templateEngine); + + Map headers = new HashMap<>(1); + String contentType = randomFrom("application/json", "application/json; charset=UTF-8", "text/html", "application/yaml", + "application/smile", "application/cbor"); + headers.put("Content-Type", new String[] { contentType }); + String body = "{\"foo\":\"bar\"}"; + HttpResponse httpResponse = new HttpResponse(200, body, headers); + when(httpClient.execute(any())).thenReturn(httpResponse); + + HttpInput.Result result = input.execute(createWatchExecutionContext(), Payload.EMPTY); + assertThat(result.payload().data(), hasEntry("_value", body)); + assertThat(result.payload().data(), not(hasKey("foo"))); + } + private WatchExecutionContext createWatchExecutionContext() { Watch watch = new Watch("test-watch", new ScheduleTrigger(new IntervalSchedule(new IntervalSchedule.Interval(1, IntervalSchedule.Interval.Unit.MINUTES))), From f42f8cf756cf9ecbcd6850454cde747763ff05a6 Mon Sep 17 00:00:00 2001 From: jaymode Date: Thu, 7 Jul 2016 07:41:48 -0400 Subject: [PATCH 4/9] security: add tool to simplify creation of certificate and csr files This commit adds a CLI tool that can be used to generate a CA and signed certificates in PEM format. The tool only requires a name of an instance to be provided by the user; ip and dns values are supported but optional. By default, the tool is interactive and will prompt the user for input but an option exists to provide a yaml file that contains the necessary information to generate certificates or signing requests. The output is in the form of a zip file with subfolders for each instance. Neither the zip file or the PEM files are encrypted as some parts of our stack do not support encrypted PEM files. Original commit: elastic/x-pack-elasticsearch@3dc0f8d495bd73b7efce4c92ad85c410294e980b --- .../x-pack/security/bin/x-pack/certgen | 104 +++ .../x-pack/security/bin/x-pack/certgen.bat | 9 + .../x-pack/security/bin/x-pack/migrate | 16 +- .../x-pack/security/bin/x-pack/syskeygen | 16 +- .../x-pack/security/bin/x-pack/users | 16 +- .../xpack/security/ssl/CertUtils.java | 80 ++- .../xpack/security/ssl/CertificateTool.java | 627 ++++++++++++++++++ .../xpack/security/ssl/PEMKeyConfig.java | 9 +- .../xpack/security/ssl/CertUtilsTests.java | 5 +- .../security/ssl/CertificateToolTests.java | 390 +++++++++++ .../security/ssl/SSLConfigurationTests.java | 2 +- .../security/ssl/SSLReloadIntegTests.java | 2 +- .../xpack/security/ssl/instances.yml | 11 + .../x-pack/watcher/bin/x-pack/croneval | 16 +- 14 files changed, 1250 insertions(+), 53 deletions(-) create mode 100644 elasticsearch/x-pack/security/bin/x-pack/certgen create mode 100644 elasticsearch/x-pack/security/bin/x-pack/certgen.bat create mode 100644 elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java create mode 100644 elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertificateToolTests.java create mode 100644 elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/ssl/instances.yml 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/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..a690d46bbc3 --- /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? []: "); + String dnsNames = terminal.readText("Enter DNS names for instance? []: "); + 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/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/test/java/org/elasticsearch/xpack/security/ssl/CertUtilsTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertUtilsTests.java index e36a0f108af..555d6a67fc2 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertUtilsTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertUtilsTests.java @@ -55,12 +55,11 @@ public class CertUtilsTests extends ESTestCase { } public void testGenerateKeyPair() throws Exception { - KeyPair keyPair = CertUtils.generateKeyPair(); + KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048)); assertThat(keyPair.getPrivate().getAlgorithm(), is("RSA")); assertThat(keyPair.getPublic().getAlgorithm(), is("RSA")); } - public void testReadKeysCorrectly() throws Exception { // read in keystore version Path keystorePath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"); @@ -77,7 +76,7 @@ public class CertUtilsTests extends ESTestCase { try (Reader reader = Files.newBufferedReader(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), StandardCharsets.UTF_8)) { - privateKey = CertUtils.readPrivateKey(reader, "testnode".toCharArray()); + privateKey = CertUtils.readPrivateKey(reader, "testnode"::toCharArray); } assertThat(privateKey, notNullValue()); assertThat(privateKey, equalTo(key)); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertificateToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertificateToolTests.java new file mode 100644 index 00000000000..4817f090fdb --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/CertificateToolTests.java @@ -0,0 +1,390 @@ +/* + * 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.bouncycastle.asn1.ASN1String; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.pkcs.Attribute; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.Extensions; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.elasticsearch.cli.MockTerminal; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.network.NetworkAddress; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.security.ssl.CertificateTool.CAInfo; +import org.elasticsearch.xpack.security.ssl.CertificateTool.CertificateInformation; +import org.elasticsearch.xpack.security.ssl.CertificateTool.Name; + +import javax.security.auth.x500.X500Principal; +import java.io.Reader; +import java.net.InetAddress; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.instanceOf; + +/** + * Unit tests for the tool used to simplify SSL certificate generation + */ +public class CertificateToolTests extends ESTestCase { + + public void testOutputDirectory() throws Exception { + Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build()); + Path outputDir = createTempDir(); + Path outputFile = outputDir.resolve("certs.zip"); + MockTerminal terminal = new MockTerminal(); + + // test with a user provided dir + Path resolvedOutputFile = CertificateTool.getOutputFile(terminal, outputFile.toString(), env, null); + assertEquals(outputFile, resolvedOutputFile); + assertTrue(terminal.getOutput().isEmpty()); + + // test without a user provided directory + Path userPromptedOutputFile = outputDir.resolve("csr"); + assertFalse(Files.exists(userPromptedOutputFile)); + terminal.addTextInput(userPromptedOutputFile.toString()); + resolvedOutputFile = CertificateTool.getOutputFile(terminal, null, env, "out.zip"); + assertEquals(userPromptedOutputFile, resolvedOutputFile); + assertTrue(terminal.getOutput().isEmpty()); + + // test with empty user input + String defaultFilename = randomAsciiOfLengthBetween(1, 10); + Path expectedDefaultPath = XPackPlugin.resolveConfigFile(env, defaultFilename); + terminal.addTextInput(""); + resolvedOutputFile = CertificateTool.getOutputFile(terminal, null, env, defaultFilename); + assertEquals(expectedDefaultPath, resolvedOutputFile); + assertTrue(terminal.getOutput().isEmpty()); + } + + public void testPromptingForInstanceInformation() throws Exception { + final int numberOfInstances = scaledRandomIntBetween(1, 12); + Map> instanceInput = new HashMap<>(numberOfInstances); + for (int i = 0; i < numberOfInstances; i++) { + final String name = randomAsciiOfLengthBetween(1, 32); + Map instanceInfo = new HashMap<>(); + instanceInput.put(name, instanceInfo); + instanceInfo.put("ip", randomFrom("127.0.0.1", "::1", "192.168.1.1,::1", "")); + instanceInfo.put("dns", randomFrom("localhost", "localhost.localdomain", "localhost,myhost", "")); + } + + int count = 0; + MockTerminal terminal = new MockTerminal(); + for (Entry> entry : instanceInput.entrySet()) { + terminal.addTextInput(entry.getKey()); + terminal.addTextInput(entry.getValue().get("ip")); + terminal.addTextInput(entry.getValue().get("dns")); + count++; + if (count == numberOfInstances) { + terminal.addTextInput("n"); + } else { + terminal.addTextInput("y"); + } + } + + Collection certInfos = CertificateTool.getCertificateInformationList(terminal, null, + new Environment(Settings.builder().put("path.home", createTempDir()).build())); + assertEquals(numberOfInstances, certInfos.size()); + for (CertificateInformation certInfo : certInfos) { + String name = certInfo.name.originalName; + Map instanceInfo = instanceInput.get(name); + assertNotNull("did not find map for " + name, instanceInfo); + List expectedIps = Arrays.asList(Strings.commaDelimitedListToStringArray(instanceInfo.get("ip"))); + List expectedDns = Arrays.asList(Strings.commaDelimitedListToStringArray(instanceInfo.get("dns"))); + assertEquals(expectedIps, certInfo.ipAddresses); + assertEquals(expectedDns, certInfo.dnsNames); + instanceInput.remove(name); + } + assertEquals(0, instanceInput.size()); + final String output = terminal.getOutput(); + assertTrue("Output: " + output, output.isEmpty()); + } + + public void testParsingFile() throws Exception { + Path instanceFile = getDataPath("instances.yml"); + Collection certInfos = CertificateTool.parseFile(instanceFile); + assertEquals(4, certInfos.size()); + + Map certInfosMap = + certInfos.stream().collect(Collectors.toMap((c) -> c.name.originalName, Function.identity())); + CertificateInformation certInfo = certInfosMap.get("node1"); + assertEquals(Collections.singletonList("127.0.0.1"), certInfo.ipAddresses); + assertEquals(Collections.singletonList("localhost"), certInfo.dnsNames); + + certInfo = certInfosMap.get("node2"); + assertEquals(Collections.singletonList("::1"), certInfo.ipAddresses); + assertEquals(Collections.emptyList(), certInfo.dnsNames); + + certInfo = certInfosMap.get("node3"); + assertEquals(Collections.emptyList(), certInfo.ipAddresses); + assertEquals(Collections.emptyList(), certInfo.dnsNames); + + certInfo = certInfosMap.get("CN=different value"); + assertEquals(Collections.emptyList(), certInfo.ipAddresses); + assertEquals(Collections.singletonList("node4.mydomain.com"), certInfo.dnsNames); + } + + public void testGeneratingCsr() throws Exception { + Path tempDir = createTempDir(); + Path outputFile = tempDir.resolve("out.zip"); + Path instanceFile = getDataPath("instances.yml"); + Collection certInfos = CertificateTool.parseFile(instanceFile); + assertEquals(4, certInfos.size()); + + assertFalse(Files.exists(outputFile)); + CertificateTool.generateAndWriteCsrs(outputFile, certInfos, randomFrom(1024, 2048)); + assertTrue(Files.exists(outputFile)); + + FileSystem fileSystem = FileSystems.newFileSystem(new URI("jar:" + outputFile.toUri()), Collections.emptyMap()); + Path zipRoot = fileSystem.getPath("/"); + + assertFalse(Files.exists(zipRoot.resolve("ca"))); + for (CertificateInformation certInfo : certInfos) { + String filename = certInfo.name.filename; + assertTrue(Files.exists(zipRoot.resolve(filename))); + final Path csr = zipRoot.resolve(filename + "/" + filename + ".csr"); + assertTrue(Files.exists(csr)); + assertTrue(Files.exists(zipRoot.resolve(filename + "/" + filename + ".key"))); + PKCS10CertificationRequest request = readCertificateRequest(csr); + assertEquals(certInfo.name.x500Principal.getName(), request.getSubject().toString()); + Attribute[] extensionsReq = request.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); + if (certInfo.ipAddresses.size() > 0 || certInfo.dnsNames.size() > 0) { + assertEquals(1, extensionsReq.length); + Extensions extensions = Extensions.getInstance(extensionsReq[0].getAttributeValues()[0]); + GeneralNames subjAltNames = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); + assertSubjAltNames(subjAltNames, certInfo); + } else { + assertEquals(0, extensionsReq.length); + } + } + } + + public void testGeneratingSignedCertificates() throws Exception { + Path tempDir = createTempDir(); + Path outputFile = tempDir.resolve("out.zip"); + Path instanceFile = getDataPath("instances.yml"); + Collection certInfos = CertificateTool.parseFile(instanceFile); + assertEquals(4, certInfos.size()); + + final int keysize = randomFrom(1024, 2048); + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + X509Certificate caCert = CertUtils.generateCACertificate(new X500Principal("CN=test ca"), keyPair); + + final boolean generatedCa = randomBoolean(); + final char[] keyPassword = randomBoolean() ? "changeme".toCharArray() : null; + assertFalse(Files.exists(outputFile)); + CAInfo caInfo = new CAInfo(caCert, keyPair.getPrivate(), generatedCa, keyPassword); + CertificateTool.generateAndWriteSignedCertificates(outputFile, certInfos, caInfo, keysize); + assertTrue(Files.exists(outputFile)); + + FileSystem fileSystem = FileSystems.newFileSystem(new URI("jar:" + outputFile.toUri()), Collections.emptyMap()); + Path zipRoot = fileSystem.getPath("/"); + + if (generatedCa) { + assertTrue(Files.exists(zipRoot.resolve("ca"))); + assertTrue(Files.exists(zipRoot.resolve("ca").resolve("ca.crt"))); + assertTrue(Files.exists(zipRoot.resolve("ca").resolve("ca.key"))); + // check the CA cert + try (Reader reader = Files.newBufferedReader(zipRoot.resolve("ca").resolve("ca.crt"))) { + X509Certificate parsedCaCert = readX509Certificate(reader); + assertThat(parsedCaCert.getSubjectX500Principal().getName(), containsString("test ca")); + assertEquals(caCert, parsedCaCert); + } + + // check the CA key + if (keyPassword != null) { + try (Reader reader = Files.newBufferedReader(zipRoot.resolve("ca").resolve("ca.key"))) { + PEMParser pemParser = new PEMParser(reader); + Object parsed = pemParser.readObject(); + assertThat(parsed, instanceOf(PEMEncryptedKeyPair.class)); + char[] zeroChars = new char[keyPassword.length]; + Arrays.fill(zeroChars, (char) 0); + assertArrayEquals(zeroChars, keyPassword); + } + } + + try (Reader reader = Files.newBufferedReader(zipRoot.resolve("ca").resolve("ca.key"))) { + PrivateKey privateKey = CertUtils.readPrivateKey(reader, () -> keyPassword != null ? "changeme".toCharArray() : null); + assertEquals(caInfo.privateKey, privateKey); + } + } else { + assertFalse(Files.exists(zipRoot.resolve("ca"))); + } + + for (CertificateInformation certInfo : certInfos) { + String filename = certInfo.name.filename; + assertTrue(Files.exists(zipRoot.resolve(filename))); + final Path cert = zipRoot.resolve(filename + "/" + filename + ".crt"); + assertTrue(Files.exists(cert)); + assertTrue(Files.exists(zipRoot.resolve(filename + "/" + filename + ".key"))); + try (Reader reader = Files.newBufferedReader(cert)) { + X509Certificate certificate = readX509Certificate(reader); + assertEquals(certInfo.name.x500Principal.toString(), certificate.getSubjectX500Principal().getName()); + final int sanCount = certInfo.ipAddresses.size() + certInfo.dnsNames.size(); + if (sanCount == 0) { + assertNull(certificate.getSubjectAlternativeNames()); + } else { + X509CertificateHolder x509CertHolder = new X509CertificateHolder(certificate.getEncoded()); + GeneralNames subjAltNames = + GeneralNames.fromExtensions(x509CertHolder.getExtensions(), Extension.subjectAlternativeName); + assertSubjAltNames(subjAltNames, certInfo); + } + } + } + } + + public void testGetCAInfo() throws Exception { + Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build()); + Path testNodeCertPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"); + Path testNodeKeyPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"); + final boolean passwordPrompt = randomBoolean(); + MockTerminal terminal = new MockTerminal(); + if (passwordPrompt) { + terminal.addSecretInput("testnode"); + } + + CAInfo caInfo = CertificateTool.getCAInfo(terminal, "CN=foo", testNodeCertPath.toString(), testNodeKeyPath.toString(), + passwordPrompt ? null : "testnode".toCharArray(), passwordPrompt, env, randomFrom(1024, 2048)); + assertTrue(terminal.getOutput().isEmpty()); + assertThat(caInfo.caCert, instanceOf(X509Certificate.class)); + assertEquals(caInfo.caCert.getSubjectX500Principal().getName(), + "CN=Elasticsearch Test Node,OU=elasticsearch,O=org"); + assertThat(caInfo.privateKey.getAlgorithm(), containsString("RSA")); + assertEquals(2048, ((RSAKey) caInfo.privateKey).getModulus().bitLength()); + assertFalse(caInfo.generated); + + // test generation + final boolean passwordProtected = randomBoolean(); + final char[] password; + if (passwordPrompt && passwordProtected) { + password = null; + terminal.addSecretInput("testnode"); + } else { + password = "testnode".toCharArray(); + } + final int keysize = randomFrom(1024, 2048); + caInfo = CertificateTool.getCAInfo(terminal, "CN=foo bar", null, null, password, passwordProtected && passwordPrompt, env, keysize); + assertTrue(terminal.getOutput().isEmpty()); + assertThat(caInfo.caCert, instanceOf(X509Certificate.class)); + assertEquals(caInfo.caCert.getSubjectX500Principal().getName(), "CN=foo bar"); + assertThat(caInfo.privateKey.getAlgorithm(), containsString("RSA")); + assertTrue(caInfo.generated); + assertEquals(keysize, ((RSAKey) caInfo.privateKey).getModulus().bitLength()); + } + + public void testNameValues() throws Exception { + // good name + Name name = Name.fromUserProvidedName("my instance"); + assertEquals("my instance", name.originalName); + assertNull(name.error); + assertEquals("CN=my instance", name.x500Principal.getName()); + assertEquals("my instance", name.originalName); + + // too long + String userProvidedName = randomAsciiOfLength(CertificateTool.MAX_FILENAME_LENGTH + 1); + name = Name.fromUserProvidedName(userProvidedName); + assertEquals(userProvidedName, name.originalName); + assertNull(name.error); + assertEquals(userProvidedName.substring(0, CertificateTool.MAX_FILENAME_LENGTH), name.filename); + assertEquals("CN=" + userProvidedName, name.x500Principal.getName()); + + // too short + name = Name.fromUserProvidedName(""); + assertEquals("", name.originalName); + assertThat(name.error, containsString("valid filename")); + assertEquals("CN=", name.x500Principal.getName()); + assertNull(name.filename); + + // invalid characters only + userProvidedName = "<>|<>*|?\"\\"; + name = Name.fromUserProvidedName(userProvidedName); + assertEquals(userProvidedName, name.originalName); + assertThat(name.error, containsString("valid DN")); + assertNull(name.x500Principal); + assertNull(name.filename); + + // invalid for file but DN ok + userProvidedName = "*"; + name = Name.fromUserProvidedName(userProvidedName); + assertEquals(userProvidedName, name.originalName); + assertThat(name.error, containsString("valid filename")); + assertEquals("CN=" + userProvidedName, name.x500Principal.getName()); + assertNull(name.filename); + + // invalid with valid chars + userProvidedName = "*.mydomain.com"; + name = Name.fromUserProvidedName(userProvidedName); + assertEquals(userProvidedName, name.originalName); + assertNull(name.error); + assertEquals("CN=" + userProvidedName, name.x500Principal.getName()); + assertEquals(".mydomain.com", name.filename); + } + + private PKCS10CertificationRequest readCertificateRequest(Path path) throws Exception { + try (Reader reader = Files.newBufferedReader(path); + PEMParser pemParser = new PEMParser(reader)) { + Object object = pemParser.readObject(); + assertThat(object, instanceOf(PKCS10CertificationRequest.class)); + return (PKCS10CertificationRequest) object; + } + } + + private X509Certificate readX509Certificate(Reader reader) throws Exception { + List list = new ArrayList<>(1); + CertUtils.readCertificates(reader, list, CertificateFactory.getInstance("X.509")); + assertEquals(1, list.size()); + assertThat(list.get(0), instanceOf(X509Certificate.class)); + return (X509Certificate) list.get(0); + } + + private void assertSubjAltNames(GeneralNames subjAltNames, CertificateInformation certInfo) throws Exception { + assertEquals(certInfo.ipAddresses.size() + certInfo.dnsNames.size(), subjAltNames.getNames().length); + Collections.sort(certInfo.dnsNames); + Collections.sort(certInfo.ipAddresses); + for (GeneralName generalName : subjAltNames.getNames()) { + if (generalName.getTagNo() == GeneralName.dNSName) { + String dns = ((ASN1String)generalName.getName()).getString(); + assertThat(Collections.binarySearch(certInfo.dnsNames, dns), greaterThanOrEqualTo(0)); + } else if (generalName.getTagNo() == GeneralName.iPAddress) { + byte[] ipBytes = DEROctetString.getInstance(generalName.getName()).getOctets(); + String ip = NetworkAddress.format(InetAddress.getByAddress(ipBytes)); + assertThat(Collections.binarySearch(certInfo.ipAddresses, ip), greaterThanOrEqualTo(0)); + } else { + fail("unknown general name with tag " + generalName.getTagNo()); + } + } + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLConfigurationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLConfigurationTests.java index 4717634c0b3..cc5cc32cdf8 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLConfigurationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLConfigurationTests.java @@ -421,7 +421,7 @@ public class SSLConfigurationTests extends ESTestCase { } }); Path updatedKeyPath = tempDir.resolve("updated.pem"); - KeyPair keyPair = CertUtils.generateKeyPair(); + KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048)); try (OutputStream os = Files.newOutputStream(updatedKeyPath); OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8); JcaPEMWriter writer = new JcaPEMWriter(osWriter)) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLReloadIntegTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLReloadIntegTests.java index db304be05bd..9301b3d4739 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLReloadIntegTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/ssl/SSLReloadIntegTests.java @@ -93,7 +93,7 @@ public class SSLReloadIntegTests extends SecurityIntegTestCase { } public void testThatSSLConfigurationReloadsOnModification() throws Exception { - KeyPair keyPair = CertUtils.generateKeyPair(); + KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048)); X509Certificate certificate = getCertificate(keyPair); KeyStore keyStore = KeyStore.getInstance("jks"); keyStore.load(null, null); diff --git a/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/ssl/instances.yml b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/ssl/instances.yml new file mode 100644 index 00000000000..0324743ebb6 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/ssl/instances.yml @@ -0,0 +1,11 @@ +instances: + - name: "node1" + ip: + - "127.0.0.1" + dns: "localhost" + - name: "node2" + ip: "::1" + - name: "node3" + - name: "CN=different value" + dns: + - "node4.mydomain.com" diff --git a/elasticsearch/x-pack/watcher/bin/x-pack/croneval b/elasticsearch/x-pack/watcher/bin/x-pack/croneval index 4194b2d0dc6..422680520dc 100755 --- a/elasticsearch/x-pack/watcher/bin/x-pack/croneval +++ b/elasticsearch/x-pack/watcher/bin/x-pack/croneval @@ -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` From 5be383288949c8259341d244f79db74afc29ca77 Mon Sep 17 00:00:00 2001 From: jaymode Date: Wed, 1 Jun 2016 09:53:35 -0400 Subject: [PATCH 5/9] security: add metadata to roles This commit adds the ability to define metadata for roles. This metadata is currently only used for the API and to indicate that a role is reserved. We can continue passing on the metadata as needed, when necessary. Closes elastic/elasticsearch#2036 Original commit: elastic/x-pack-elasticsearch@8b5f6061382142eef5fcbc403ed30eba4e04f9ca --- .../security/SecurityTemplateService.java | 3 +- .../security/action/role/PutRoleRequest.java | 21 +++++- .../action/role/PutRoleRequestBuilder.java | 8 +++ .../xpack/security/authz/RoleDescriptor.java | 53 ++++++++++---- .../security/authz/permission/KibanaRole.java | 4 +- .../authz/permission/KibanaUserRole.java | 4 +- .../authz/permission/SuperuserRole.java | 4 +- .../authz/permission/TransportClientRole.java | 4 +- .../xpack/security/support/MetadataUtils.java | 71 +++++++++++++++++++ .../xpack/security/user/User.java | 60 ++-------------- .../resources/security-index-template.json | 4 ++ .../ESNativeRealmMigrateToolTests.java | 2 +- .../authc/esnative/NativeRealmIntegTests.java | 6 +- .../security/authz/RoleDescriptorTests.java | 41 ++++++++++- .../xpack/security/user/UserTests.java | 5 +- .../rest-api-spec/test/roles/10_basic.yaml | 6 ++ 16 files changed, 215 insertions(+), 81 deletions(-) create mode 100644 elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/MetadataUtils.java 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 2446ab64833..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 @@ -82,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/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/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/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/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/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/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/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/user/UserTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/user/UserTests.java index 8fe4af64fb7..08f42022328 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/user/UserTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/user/UserTests.java @@ -7,10 +7,9 @@ package org.elasticsearch.xpack.security.user; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.io.stream.ByteBufferStreamInput; import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.xpack.security.support.MetadataUtils; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.XPackClient; import java.util.Arrays; import java.util.Collections; @@ -127,7 +126,7 @@ public class UserTests extends ESTestCase { public void testReservedMetadata() throws Exception { Map validMetadata = Collections.singletonMap("foo", "bar"); - Map invalidMetadata = Collections.singletonMap(User.RESERVED_PREFIX + "foo", "bar"); + Map invalidMetadata = Collections.singletonMap(MetadataUtils.RESERVED_PREFIX + "foo", "bar"); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> new User("john", Strings.EMPTY_ARRAY, "John Doe", "john@doe.com", invalidMetadata)); diff --git a/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/roles/10_basic.yaml b/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/roles/10_basic.yaml index 21498c69b0d..220a0100b1d 100644 --- a/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/roles/10_basic.yaml +++ b/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/roles/10_basic.yaml @@ -38,6 +38,10 @@ teardown: body: > { "cluster": ["all"], + "metadata": { + "key1" : "val1", + "key2" : "val2" + }, "indices": [ { "names": "*", @@ -73,5 +77,7 @@ teardown: xpack.security.get_role: name: "admin_role" - match: { admin_role.cluster.0: "all" } + - match: { admin_role.metadata.key1: "val1" } + - match: { admin_role.metadata.key2: "val2" } - match: { admin_role.indices.0.names.0: "*" } - match: { admin_role.indices.0.privileges.0: "all" } From 59fcb205b59adf96b31fb279f6a91dc28e7d8b5c Mon Sep 17 00:00:00 2001 From: jaymode Date: Thu, 16 Jun 2016 14:34:04 -0400 Subject: [PATCH 6/9] security: active directory and ldap realm improvements This commit is a combination of enhancements and fixes to the active directory and ldap realms. The active directory realm has been enhanced to add support for authentication against multiple domains in a forest. The ldap realm has been updated so that: * attributes required for group resolution are loaded eagerly if possible * user search can now be executed using unpooled connections * the default search filter for groups now includes posixGroup and memberUid to avoid users needed to understand ldap filters Finally, the UnboundID LDAP SDK was upgraded to the latest version and some long standing AwaitsFix were addressed. Closes elastic/elasticsearch#20 Closes elastic/elasticsearch#26 Closes elastic/elasticsearch#1950 Closes elastic/elasticsearch#2145 Closes elastic/elasticsearch#2363 Original commit: elastic/x-pack-elasticsearch@63c9be2337032174e1f1f7f8a517719b1718b8bc --- elasticsearch/x-pack/build.gradle | 2 +- .../ActiveDirectoryGroupsResolver.java | 26 +- .../activedirectory/ActiveDirectoryRealm.java | 2 +- .../ActiveDirectorySessionFactory.java | 261 +++++++++++++++--- .../xpack/security/authc/ldap/LdapRealm.java | 10 +- .../authc/ldap/LdapSessionFactory.java | 24 +- .../ldap/LdapUserSearchSessionFactory.java | 178 ++++++------ .../authc/ldap/SearchGroupsResolver.java | 92 +++--- .../ldap/UserAttributeGroupsResolver.java | 50 ++-- .../authc/ldap/support/AbstractLdapRealm.java | 3 +- .../authc/ldap/support/LdapSession.java | 16 +- .../authc/ldap/support/LdapUtils.java | 8 +- .../authc/ldap/support/SessionFactory.java | 28 +- .../ActiveDirectoryGroupsResolverTests.java | 26 +- .../ActiveDirectoryRealmTests.java | 14 +- .../ActiveDirectoryRealmUsageTests.java | 2 +- .../ActiveDirectorySessionFactoryTests.java | 79 +++--- .../authc/ldap/GroupsResolverTestCase.java | 1 - .../security/authc/ldap/LdapRealmTests.java | 14 +- .../authc/ldap/LdapSessionFactoryTests.java | 45 ++- .../LdapUserSearchSessionFactoryTests.java | 101 +++---- .../security/authc/ldap/OpenLdapTests.java | 35 +-- .../authc/ldap/SearchGroupsResolverTests.java | 28 +- .../UserAttributeGroupsResolverTests.java | 6 +- .../SessionFactoryLoadBalancingTests.java | 2 +- .../ldap/support/SessionFactoryTests.java | 2 +- .../netty3/HandshakeWaitingHandlerTests.java | 11 +- 27 files changed, 660 insertions(+), 406 deletions(-) diff --git a/elasticsearch/x-pack/build.gradle b/elasticsearch/x-pack/build.gradle index 0a96f2c27ab..a6ea623e29d 100644 --- a/elasticsearch/x-pack/build.gradle +++ b/elasticsearch/x-pack/build.gradle @@ -29,7 +29,7 @@ dependencies { // 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' 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 1596ba47d53..144b3c72f21 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 @@ -41,7 +41,7 @@ public class ActiveDirectoryRealm extends AbstractLdapRealm { @Override public ActiveDirectoryRealm create(RealmConfig config) { - ActiveDirectorySessionFactory connectionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory connectionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); return new ActiveDirectoryRealm(config, connectionFactory, 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/ldap/LdapRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index cc1a739f36d..b846c4019ef 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,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; @@ -16,7 +17,6 @@ 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; /** @@ -55,12 +55,12 @@ public class LdapRealm extends AbstractLdapRealm { SessionFactory sessionFactory = sessionFactory(config, clientSSLService); DnRoleMapper roleMapper = new DnRoleMapper(TYPE, config, watcherService, null); return new LdapRealm(config, sessionFactory, roleMapper); - } catch (IOException e) { + } catch (LDAPException e) { throw new ElasticsearchException("failed to create realm [{}/{}]", e, LdapRealm.TYPE, config.name()); } } - static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws IOException { + static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) throws LDAPException { Settings searchSettings = userSearchSettings(config); if (!searchSettings.names().isEmpty()) { if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { @@ -68,9 +68,9 @@ public class LdapRealm extends AbstractLdapRealm { "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 LdapUserSearchSessionFactory(config, clientSSLService); } - return new LdapSessionFactory(config, clientSSLService).init(); + return new LdapSessionFactory(config, clientSSLService); } static Settings userSearchSettings(RealmConfig config) { 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..e7c2d99b71c 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 @@ -54,13 +54,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 +62,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 68ef7ea0e7f..f8a32d231a7 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,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap.support; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.rest.RestController; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.RealmConfig; @@ -88,7 +89,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()])); 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/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/ldap/GroupsResolverTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java index bec22ac6e9a..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 @@ -45,7 +45,6 @@ public abstract class GroupsResolverTestCase extends ESTestCase { 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..6e2f159c6ee 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))); @@ -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..f5cd1250fc3 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; @@ -22,6 +22,7 @@ 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 +50,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 +58,14 @@ 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") 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 87fb66a8a5c..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; @@ -87,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 { @@ -107,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"); @@ -142,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"); @@ -181,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"); @@ -216,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"); @@ -255,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"); @@ -289,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"); @@ -326,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"); @@ -361,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 { @@ -403,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 { @@ -479,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() @@ -488,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(); + } } } @@ -512,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") @@ -525,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 95da2786d0d..d3c7facba65 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; @@ -67,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) { @@ -84,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) { @@ -111,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(); @@ -133,32 +135,28 @@ 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") 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 { @@ -171,14 +169,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/transport/netty3/HandshakeWaitingHandlerTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java index 2be39ee278f..7f40fff068d 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/HandshakeWaitingHandlerTests.java @@ -125,15 +125,17 @@ public class HandshakeWaitingHandlerTests extends ESTestCase { } if (failureCause.get() != null) { + logger.info("run [{}] produced a failure", i); assertThat(failureCause.get(), anyOf(instanceOf(SSLException.class), instanceOf(AssertionError.class))); break; + } else { + logger.warn("run [{}] did not produce a failure", i); } } assertThat("Expected this test to fail with an SSLException or AssertionError", failureCause.get(), notNullValue()); } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/533") public void testWriteBeforeHandshakePassesWithHandshakeWaitingHandler() throws Exception { clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() { @@ -161,14 +163,15 @@ public class HandshakeWaitingHandlerTests extends ESTestCase { // Wait for pending writes to prevent IOExceptions Channel channel = handshakeFuture.getChannel(); HandshakeWaitingHandler handler = channel.getPipeline().get(HandshakeWaitingHandler.class); - while (handler != null && handler.hasPendingWrites()) { - Thread.sleep(10); + if (handler != null) { + boolean noMoreWrites = awaitBusy(() -> handler.hasPendingWrites() == false); + assertTrue(noMoreWrites); } - channel.close(); } assertNotFailed(); + logger.info("run [{}] succeeded", i); } } From 34d04a8c780938a0819d3c7ed4df5bab3e24738c Mon Sep 17 00:00:00 2001 From: jaymode Date: Mon, 18 Jul 2016 08:53:17 -0400 Subject: [PATCH 7/9] security: mention comma-separated for IP and DNS name prompts Original commit: elastic/x-pack-elasticsearch@3e58fc282a0b47c1021fc4a6dfbd0cb6eb38aa17 --- .../org/elasticsearch/xpack/security/ssl/CertificateTool.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index a690d46bbc3..df1d35b4717 100644 --- 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 @@ -189,8 +189,8 @@ public class CertificateTool extends SettingCommand { while (done == false) { String name = terminal.readText("Enter instance name: "); if (name.isEmpty() == false) { - String ipAddresses = terminal.readText("Enter IP Addresses for instance? []: "); - String dnsNames = terminal.readText("Enter DNS names for instance? []: "); + 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)); From 12c709ea3ade9ea879548d3fc0fd3efb004a98c0 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Mon, 18 Jul 2016 15:43:29 +0200 Subject: [PATCH 8/9] Move over to dedicated TransportClient implementations (elastic/elasticsearch#2819) Followup of elastic/elasticsearchelastic/elasticsearch#19435 Relates to elastic/elasticsearchelastic/elasticsearch#19412 Original commit: elastic/x-pack-elasticsearch@60f7047ea980cff68905ed244d7984332ad795ee --- .../qa/SecurityTransportClientIT.java | 3 +- .../example/realm/CustomRealmIT.java | 6 ++-- .../xpack/security/MigrateToolTestCase.java | 11 ++----- .../security/audit/index/IndexAuditTrail.java | 11 ++----- .../integration/LicensingTests.java | 5 +-- .../index/IndexAuditTrailMutedTests.java | 4 +-- .../xpack/security/authc/RunAsIntegTests.java | 7 ++--- .../authc/pki/PkiAuthenticationTests.java | 4 +-- .../authc/pki/PkiOptionalClientAuthTests.java | 4 +-- .../netty3/SslHostnameVerificationTests.java | 4 +-- .../transport/ssl/SslClientAuthTests.java | 4 +-- .../transport/ssl/SslIntegrationTests.java | 10 +++--- .../transport/ssl/SslMultiPortTests.java | 31 +++++++++---------- .../xpack/XPackTransportClient.java | 30 ++++++++++++++++++ 14 files changed, 75 insertions(+), 59 deletions(-) create mode 100644 elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackTransportClient.java 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/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..e3db177f930 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; @@ -78,7 +80,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 +100,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/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 bf72ecb2efc..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,10 +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.test.ESIntegTestCase; -import org.elasticsearch.xpack.security.Security; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -26,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; /** @@ -81,10 +77,7 @@ public abstract class MigrateToolTestCase extends LuceneTestCase { .put(Security.USER_SETTING.getKey(), "transport_user:changeme") .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/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 fb3b0d771fa..329c20777ed 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 @@ -28,7 +28,6 @@ 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; @@ -56,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; @@ -737,12 +735,9 @@ public class IndexAuditTrail extends AbstractComponent implements AuditTrail, Cl + 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() + final TransportClient transportClient = new XPackTransportClient(Settings.builder() .put("node.name", DEFAULT_CLIENT_NAME + "-" + Node.NODE_NAME_SETTING.get(settings)) - .put(theClientSetting)) - .addPlugin(XPackPlugin.class) - .build(); + .put(theClientSetting).build()); for (Tuple pair : hostPortPairs) { try { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(pair.v1()), pair.v2())); 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 index ec93c9367c3..cb60bfc7368 100644 --- 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 @@ -48,6 +48,7 @@ import org.elasticsearch.xpack.MockNetty3Plugin; import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.graph.GraphLicensee; import org.elasticsearch.xpack.monitoring.MonitoringLicensee; +import org.elasticsearch.xpack.XPackTransportClient; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.SecurityLicenseState; import org.elasticsearch.xpack.security.SecurityLicensee; @@ -208,7 +209,7 @@ public class LicensingTests extends SecurityIntegTestCase { builder.remove(ThreadContext.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER); // basic has no auth - try (TransportClient client = TransportClient.builder().settings(builder).addPlugin(XPackPlugin.class).build()) { + try (TransportClient client = new XPackTransportClient(builder.build())) { client.addTransportAddress(internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress()); assertGreenClusterState(client); } @@ -217,7 +218,7 @@ public class LicensingTests extends SecurityIntegTestCase { OperationMode mode = randomFrom(OperationMode.GOLD, OperationMode.TRIAL, OperationMode.PLATINUM, OperationMode.STANDARD); enableLicensing(mode); - try (TransportClient client = TransportClient.builder().settings(builder).addPlugin(XPackPlugin.class).build()) { + 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!"); 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 48cecff1525..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 @@ -16,7 +16,6 @@ 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; @@ -26,6 +25,7 @@ import org.elasticsearch.rest.RestRequest; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; +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; @@ -60,7 +60,7 @@ public class IndexAuditTrailMutedTests extends ESTestCase { when(clusterService.localNode()).thenReturn(localNode); threadPool = new TestThreadPool("index audit trail tests"); - transportClient = TransportClient.builder().settings(Settings.builder().put("transport.type", "local")).build(); + transportClient = new MockTransportClient(Settings.EMPTY); clientCalled = new AtomicBoolean(false); class IClient extends InternalClient { IClient(Client transportClient){ 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 22bd07fe837..bf72fb46915 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 @@ -23,7 +23,7 @@ import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; 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; @@ -233,10 +233,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase { .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/pki/PkiAuthenticationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java index 4285cc91a14..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 @@ -25,7 +25,7 @@ import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServe 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; @@ -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 26ced1edfe6..9cf49195ac7 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 @@ -18,7 +18,7 @@ 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; @@ -112,7 +112,7 @@ public class PkiOptionalClientAuthTests extends SecurityIntegTestCase { .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/transport/netty3/SslHostnameVerificationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/SslHostnameVerificationTests.java index 099c801f6c1..a5fcfbfae36 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/SslHostnameVerificationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty3/SslHostnameVerificationTests.java @@ -13,8 +13,8 @@ import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.security.Security; +import org.elasticsearch.xpack.XPackTransportClient; import java.net.InetSocketAddress; import java.nio.file.Files; @@ -99,7 +99,7 @@ public class SslHostnameVerificationTests extends SecurityIntegTestCase { .put(SecurityNetty3Transport.HOSTNAME_VERIFICATION_SETTING.getKey(), true) .build(); - try (TransportClient client = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient client = new XPackTransportClient(settings)) { client.addTransportAddress(new InetSocketTransportAddress(inetSocketAddress.getAddress(), inetSocketAddress.getPort())); client.admin().cluster().prepareHealth().get(); fail("Expected a NoNodeAvailableException due to hostname verification failures"); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslClientAuthTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslClientAuthTests.java index 124c93e3f51..bedfbe4cd69 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslClientAuthTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslClientAuthTests.java @@ -20,7 +20,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.SecurityIntegTestCase; 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.ssl.ClientSSLService; import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; @@ -101,7 +101,7 @@ public class SslClientAuthTests extends SecurityIntegTestCase { .put(Security.USER_SETTING.getKey(), transportClientUsername() + ":" + new String(transportClientPassword().internalChars())) .build(); - try (TransportClient client = TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build()) { + try (TransportClient client = new XPackTransportClient(settings)) { Transport transport = internalCluster().getDataNodeInstance(Transport.class); TransportAddress transportAddress = transport.boundAddress().publishAddress(); client.addTransportAddress(transportAddress); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java index 51097bf9009..1fb2bd8f71a 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslIntegrationTests.java @@ -28,7 +28,7 @@ import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; @@ -59,12 +59,12 @@ public class SslIntegrationTests extends SecurityIntegTestCase { // no SSL exception as this is the exception is returned when connecting public void testThatUnconfiguredCiphersAreRejected() { - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(Settings.builder() + try (TransportClient transportClient = new XPackTransportClient(Settings.builder() .put(transportClientSettings()) .put("node.name", "programmatic_transport_client") .put("cluster.name", internalCluster().getClusterName()) .putArray("xpack.security.ssl.ciphers", new String[]{"TLS_ECDH_anon_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"}) - .build()).build()) { + .build())) { TransportAddress transportAddress = randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses()); transportClient.addTransportAddress(transportAddress); @@ -78,12 +78,12 @@ public class SslIntegrationTests extends SecurityIntegTestCase { // no SSL exception as this is the exception is returned when connecting public void testThatTransportClientUsingSSLv3ProtocolIsRejected() { - try(TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(Settings.builder() + try(TransportClient transportClient = new XPackTransportClient(Settings.builder() .put(transportClientSettings()) .put("node.name", "programmatic_transport_client") .put("cluster.name", internalCluster().getClusterName()) .putArray("xpack.security.ssl.supported_protocols", new String[]{"SSLv3"}) - .build()).build()) { + .build())) { TransportAddress transportAddress = randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses()); transportClient.addTransportAddress(transportAddress); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java index 9b243d4192e..3f95dee6380 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/ssl/SslMultiPortTests.java @@ -12,10 +12,9 @@ import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; -import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.transport.Transport; -import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackTransportClient; import org.junit.BeforeClass; import java.net.InetAddress; @@ -105,9 +104,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put("cluster.name", internalCluster().getClusterName()) .put(additionalSettings) .build(); - return TransportClient.builder().settings(settings) - .addPlugin(XPackPlugin.class) - .build(); + return new XPackTransportClient(settings); } /** @@ -240,7 +237,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put(SecurityNetty3Transport.SSL_SETTING.getKey(), false) .put("cluster.name", internalCluster().getClusterName()) .build(); - try (TransportClient transportClient = TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); assertGreenClusterState(transportClient); } @@ -255,7 +252,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) .put("cluster.name", internalCluster().getClusterName()) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses())); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -273,7 +270,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) .put("cluster.name", internalCluster().getClusterName()) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("client"))); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -291,7 +288,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put(Security.USER_SETTING.getKey(), DEFAULT_USER_NAME + ":" + DEFAULT_PASSWORD) .put("cluster.name", internalCluster().getClusterName()) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_client_auth"))); assertGreenClusterState(transportClient); @@ -315,7 +312,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) .put("xpack.security.ssl.truststore.password", "truststore-testnode-only") .build(); - try (TransportClient transportClient = TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_client_auth"))); assertGreenClusterState(transportClient); @@ -337,7 +334,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) .put("xpack.security.ssl.truststore.password", "truststore-testnode-only") .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("client"))); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -361,7 +358,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) .put("xpack.security.ssl.truststore.password", "truststore-testnode-only") .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses())); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -384,7 +381,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/truststore-testnode-only.jks")) .put("xpack.security.ssl.truststore.password", "truststore-testnode-only") .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -404,7 +401,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put("cluster.name", internalCluster().getClusterName()) .put(SecurityNetty3Transport.SSL_SETTING.getKey(), true) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(randomFrom(internalCluster().getInstance(Transport.class).boundAddress().boundAddresses())); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -424,7 +421,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put("cluster.name", internalCluster().getClusterName()) .put(SecurityNetty3Transport.SSL_SETTING.getKey(), true) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("client"))); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); @@ -444,7 +441,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put("cluster.name", internalCluster().getClusterName()) .put(SecurityNetty3Transport.SSL_SETTING.getKey(), true) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_client_auth"))); assertGreenClusterState(transportClient); @@ -465,7 +462,7 @@ public class SslMultiPortTests extends SecurityIntegTestCase { .put("cluster.name", internalCluster().getClusterName()) .put(SecurityNetty3Transport.SSL_SETTING.getKey(), true) .build(); - try (TransportClient transportClient = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) { + try (TransportClient transportClient = new XPackTransportClient(settings)) { transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), getProfilePort("no_ssl"))); assertGreenClusterState(transportClient); fail("Expected NoNodeAvailableException"); diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackTransportClient.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackTransportClient.java new file mode 100644 index 00000000000..53aed6bf621 --- /dev/null +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackTransportClient.java @@ -0,0 +1,30 @@ +/* + * 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; + +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.plugins.Plugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +/** + * TransportClient.Builder that installs the XPackPlugin by default. + */ +@SuppressWarnings({"unchecked","varargs"}) +public class XPackTransportClient extends TransportClient { + + @SafeVarargs + public XPackTransportClient(Settings settings, Class... plugins) { + this(settings, Arrays.asList(plugins)); + } + + public XPackTransportClient(Settings settings, Collection> plugins) { + super(settings, Settings.EMPTY, addPlugins(plugins, XPackPlugin.class)); + } +} From 67f473a9927621bdbfc34fdd3df8dfca156cd358 Mon Sep 17 00:00:00 2001 From: jaymode Date: Mon, 18 Jul 2016 11:20:52 -0400 Subject: [PATCH 9/9] test: mute ldap timeout tests See elastic/elasticsearch#2849 Original commit: elastic/x-pack-elasticsearch@318307073e02ca7e2bcf5df4e0ef29e5f99261bc --- .../xpack/security/authc/ldap/LdapSessionFactory.java | 2 -- .../xpack/security/authc/ldap/LdapSessionFactoryTests.java | 2 +- .../elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) 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 e7c2d99b71c..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; 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 f5cd1250fc3..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 @@ -19,7 +19,6 @@ 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; @@ -66,6 +65,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { } @Network + @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"; 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 d3c7facba65..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 @@ -142,6 +142,7 @@ public class OpenLdapTests extends ESTestCase { } } + @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";