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/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java index 99903025602..f84c4cc74ad 100644 --- a/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java +++ b/elasticsearch/qa/messy-test-xpack-with-mustache/src/test/java/org/elasticsearch/messy/tests/SecurityCachePermissionIT.java @@ -7,14 +7,13 @@ package org.elasticsearch.messy.tests; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; -import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.indices.TermsLookup; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.script.Template; +import org.elasticsearch.script.ScriptService; import org.elasticsearch.script.mustache.MustachePlugin; -import org.elasticsearch.script.mustache.MustacheScriptEngineService; +import org.elasticsearch.script.mustache.TemplateQueryBuilder; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.security.authc.support.SecuredString; @@ -25,7 +24,6 @@ import java.util.ArrayList; import java.util.Collection; import static java.util.Collections.singletonMap; -import static org.elasticsearch.script.ScriptService.ScriptType.INLINE; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -109,7 +107,7 @@ public class SecurityCachePermissionIT extends SecurityIntegTestCase { //Template template = new Template(source, INLINE, MustacheScriptEngineService.NAME, null, singletonMap("name", "token")); SearchResponse response = client().prepareSearch("data").setTypes("a") - .setQuery(QueryBuilders.templateQuery(source, singletonMap("name", "token"))) + .setQuery(new TemplateQueryBuilder(source, ScriptService.ScriptType.INLINE, singletonMap("name", "token"))) .execute().actionGet(); assertThat(response.isTimedOut(), is(false)); assertThat(response.getHits().hits().length, is(1)); @@ -119,7 +117,7 @@ public class SecurityCachePermissionIT extends SecurityIntegTestCase { .filterWithHeader(singletonMap("Authorization", basicAuthHeaderValue(READ_ONE_IDX_USER, new SecuredString("changeme".toCharArray())))) .prepareSearch("data").setTypes("a") - .setQuery(QueryBuilders.templateQuery(source, singletonMap("name", "token"))) + .setQuery(new TemplateQueryBuilder(source, ScriptService.ScriptType.INLINE, singletonMap("name", "token"))) .execute().actionGet()); assertThat(e.toString(), containsString("ElasticsearchSecurityException[action")); assertThat(e.toString(), containsString("unauthorized")); diff --git a/elasticsearch/qa/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/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/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/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/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/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/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 19a72fe90d5..91ac8cd2993 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealm.java @@ -20,7 +20,7 @@ public class ActiveDirectoryRealm extends AbstractLdapRealm { public static final String TYPE = "active_directory"; public ActiveDirectoryRealm(RealmConfig config, ResourceWatcherService watcherService, ClientSSLService clientSSLService) { - this(config, new ActiveDirectorySessionFactory(config, clientSSLService).init(), + this(config, new ActiveDirectorySessionFactory(config, clientSSLService), new DnRoleMapper(TYPE, config, watcherService, null)); } 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 4f0fc570592..e2de7894095 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.authc.ldap; import java.util.Map; +import com.unboundid.ldap.sdk.LDAPException; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.security.authc.RealmConfig; @@ -15,6 +17,7 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.ssl.ClientSSLService; + /** * Authenticates username/password tokens against ldap, locates groups and maps them to roles. */ @@ -33,15 +36,19 @@ public class LdapRealm extends AbstractLdapRealm { static SessionFactory sessionFactory(RealmConfig config, ClientSSLService clientSSLService) { Settings searchSettings = userSearchSettings(config); - if (!searchSettings.names().isEmpty()) { - if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { - throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " + - "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap " + - "authentication section of the X-Pack guide."); + try { + if (!searchSettings.names().isEmpty()) { + if (config.settings().getAsArray(LdapSessionFactory.USER_DN_TEMPLATES_SETTING).length > 0) { + throw new IllegalArgumentException("settings were found for both user search and user template modes of operation. " + + "Please remove the settings for the mode you do not wish to use. For more details refer to the ldap " + + "authentication section of the X-Pack guide."); + } + return new LdapUserSearchSessionFactory(config, clientSSLService); } - return new LdapUserSearchSessionFactory(config, clientSSLService).init(); + return new LdapSessionFactory(config, clientSSLService); + } catch (LDAPException e) { + throw new ElasticsearchException("failed to create realm [{}/{}]", e, LdapRealm.TYPE, config.name()); } - return new LdapSessionFactory(config, clientSSLService).init(); } 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..5ffd2a52304 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactory.java @@ -14,9 +14,7 @@ import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsRes import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; -import org.elasticsearch.xpack.security.support.Exceptions; -import java.io.IOException; import java.text.MessageFormat; import java.util.Locale; @@ -54,13 +52,7 @@ public class LdapSessionFactory extends SessionFactory { */ @Override protected LdapSession getSession(String username, SecuredString password) throws Exception { - LDAPConnection connection; - - try { - connection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("failed to connect to any LDAP servers", e); - } + LDAPConnection connection = serverSet.getConnection(); LDAPException lastException = null; String passwordString = new String(password.internalChars()); @@ -68,20 +60,22 @@ public class LdapSessionFactory extends SessionFactory { String dn = buildDnFromTemplate(username, template); try { connection.bind(dn, passwordString); - return new LdapSession(connectionLogger, connection, dn, groupResolver, timeout); + return new LdapSession(logger, connection, dn, groupResolver, timeout, null); } catch (LDAPException e) { - if (logger.isDebugEnabled()) { - logger.debug("failed LDAP authentication with user template [{}] and DN [{}]", e, template, dn); + // we catch the ldapException here since we expect it can happen and we shouldn't be logging this all the time otherwise + // it is just noise + logger.debug("failed LDAP authentication with user template [{}] and DN [{}]", e, template, dn); + if (lastException == null) { + lastException = e; } else { - logger.warn("failed LDAP authentication with user template [{}] and DN [{}]: {}", template, dn, e.getMessage()); + lastException.addSuppressed(e); } - - lastException = e; } } connection.close(); - throw Exceptions.authenticationError("failed LDAP authentication", lastException); + assert lastException != null; + throw lastException; } /** diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java index fd84514a547..77ceefcc7e3 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactory.java @@ -8,12 +8,13 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.sdk.GetEntryLDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPConnectionPool; +import com.unboundid.ldap.sdk.LDAPConnectionPoolHealthCheck; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.ServerSet; import com.unboundid.ldap.sdk.SimpleBindRequest; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; @@ -26,28 +27,29 @@ import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.ssl.ClientSSLService; import org.elasticsearch.xpack.security.support.Exceptions; -import java.io.IOException; import java.util.Locale; import static com.unboundid.ldap.sdk.Filter.createEqualityFilter; import static com.unboundid.ldap.sdk.Filter.encodeValue; +import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.attributesToSearchFor; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; -public class LdapUserSearchSessionFactory extends SessionFactory { +class LdapUserSearchSessionFactory extends SessionFactory { static final int DEFAULT_CONNECTION_POOL_SIZE = 20; - static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 5; + static final int DEFAULT_CONNECTION_POOL_INITIAL_SIZE = 0; static final String DEFAULT_USERNAME_ATTRIBUTE = "uid"; static final TimeValue DEFAULT_HEALTH_CHECK_INTERVAL = TimeValue.timeValueSeconds(60L); private final String userSearchBaseDn; private final LdapSearchScope scope; private final String userAttribute; + private final GroupsResolver groupResolver; + private final boolean useConnectionPool; - private LDAPConnectionPool connectionPool; - private GroupsResolver groupResolver; + private final LDAPConnectionPool connectionPool; - public LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) { + LdapUserSearchSessionFactory(RealmConfig config, ClientSSLService sslService) throws LDAPException { super(config, sslService); Settings settings = config.settings(); userSearchBaseDn = settings.get("user_search.base_dn"); @@ -56,63 +58,52 @@ public class LdapUserSearchSessionFactory extends SessionFactory { } scope = LdapSearchScope.resolve(settings.get("user_search.scope"), LdapSearchScope.SUB_TREE); userAttribute = settings.get("user_search.attribute", DEFAULT_USERNAME_ATTRIBUTE); - } - - @Override - public LdapUserSearchSessionFactory init() { - super.init(); - connectionPool = createConnectionPool(config, serverSet, timeout, logger); groupResolver = groupResolver(config.settings()); - return this; - } - - private synchronized LDAPConnectionPool connectionPool() throws IOException { - if (connectionPool == null) { + useConnectionPool = settings.getAsBoolean("user_search.pool.enabled", true); + if (useConnectionPool) { connectionPool = createConnectionPool(config, serverSet, timeout, logger); - // if it is still null throw an exception - if (connectionPool == null) { - String msg = "failed to create a connection pool for realm [" + config.name() + "] as no LDAP servers are available"; - throw new IOException(msg); - } + } else { + connectionPool = null; } - - return connectionPool; } - static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, ESLogger logger) { + static LDAPConnectionPool createConnectionPool(RealmConfig config, ServerSet serverSet, TimeValue timeout, ESLogger logger) + throws LDAPException { Settings settings = config.settings(); SimpleBindRequest bindRequest = bindRequest(settings); - int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); - int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + final int initialSize = settings.getAsInt("user_search.pool.initial_size", DEFAULT_CONNECTION_POOL_INITIAL_SIZE); + final int size = settings.getAsInt("user_search.pool.size", DEFAULT_CONNECTION_POOL_SIZE); + LDAPConnectionPool pool = null; + boolean success = false; try { - LDAPConnectionPool pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); + pool = new LDAPConnectionPool(serverSet, bindRequest, initialSize, size); pool.setRetryFailedOperationsDueToInvalidConnections(true); if (settings.getAsBoolean("user_search.pool.health_check.enabled", true)) { String entryDn = settings.get("user_search.pool.health_check.dn", (bindRequest == null) ? null : bindRequest.getBindDN()); - if (entryDn == null) { - pool.close(); - throw new IllegalArgumentException("[bind_dn] has not been specified so a value must be specified for [user_search" + - ".pool.health_check.dn] or [user_search.pool.health_check.enabled] must be set to false"); + final long healthCheckInterval = + settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL).millis(); + if (entryDn != null) { + // Checks the status of the LDAP connection at a specified interval in the background. We do not check on + // on create as the LDAP server may require authentication to get an entry and a bind request has not been executed + // yet so we could end up never getting a connection. We do not check on checkout as we always set retry operations + // and the pool will handle a bad connection without the added latency on every operation + LDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(), + false, false, false, true, false); + pool.setHealthCheck(healthCheck); + pool.setHealthCheckIntervalMillis(healthCheckInterval); + } else { + logger.warn("[bind_dn] and [user_search.pool.health_check.dn] have not been specified so no " + + "ldap query will be run as a health check"); } - long healthCheckInterval = settings.getAsTime("user_search.pool.health_check.interval", DEFAULT_HEALTH_CHECK_INTERVAL) - .millis(); - // Checks the status of the LDAP connection at a specified interval in the background. We do not check on - // on create as the LDAP server may require authentication to get an entry. We do not check on checkout - // as we always set retry operations and the pool will handle a bad connection without the added latency on every operation - GetEntryLDAPConnectionPoolHealthCheck healthCheck = new GetEntryLDAPConnectionPoolHealthCheck(entryDn, timeout.millis(), - false, false, false, true, false); - pool.setHealthCheck(healthCheck); - pool.setHealthCheckIntervalMillis(healthCheckInterval); } + + success = true; return pool; - } catch (LDAPException e) { - if (logger.isDebugEnabled()) { - logger.debug("unable to create connection pool for realm [{}]", e, config.name()); - } else { - logger.error("unable to create connection pool for realm [{}]: {}", config.name(), e.getMessage()); + } finally { + if (success == false && pool != null) { + pool.close(); } } - return null; } static SimpleBindRequest bindRequest(Settings settings) { @@ -126,12 +117,38 @@ public class LdapUserSearchSessionFactory extends SessionFactory { @Override protected LdapSession getSession(String user, SecuredString password) throws Exception { + if (useConnectionPool) { + return getSessionWithPool(user, password); + } else { + return getSessionWithoutPool(user, password); + } + } + + private LdapSession getSessionWithPool(String user, SecuredString password) throws Exception { + SearchResultEntry searchResult = findUser(user, connectionPool); + assert searchResult != null; + final String dn = searchResult.getDN(); + connectionPool.bindAndRevertAuthentication(dn, new String(password.internalChars())); + return new LdapSession(logger, connectionPool, dn, groupResolver, timeout, searchResult.getAttributes()); + } + + private LdapSession getSessionWithoutPool(String user, SecuredString password) throws Exception { + boolean success = false; + LDAPConnection connection = null; try { - String dn = findUserDN(user); - tryBind(dn, password); - return new LdapSession(logger, connectionPool, dn, groupResolver, timeout); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to authenticate user [{}]", e, user); + connection = serverSet.getConnection(); + connection.bind(bindRequest(config.settings())); + SearchResultEntry searchResult = findUser(user, connection); + assert searchResult != null; + final String dn = searchResult.getDN(); + connection.bind(dn, new String(password.internalChars())); + LdapSession session = new LdapSession(logger, connection, dn, groupResolver, timeout, searchResult.getAttributes()); + success = true; + return session; + } finally { + if (success == false && connection != null) { + connection.close(); + } } } @@ -142,46 +159,45 @@ public class LdapUserSearchSessionFactory extends SessionFactory { @Override public LdapSession unauthenticatedSession(String user) throws Exception { + LDAPConnection connection = null; + boolean success = false; try { - String dn = findUserDN(user); - return new LdapSession(logger, connectionPool, dn, groupResolver, timeout); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed to lookup user [{}]", e, user); + final LDAPInterface ldapInterface; + if (useConnectionPool) { + ldapInterface = connectionPool; + } else { + connection = serverSet.getConnection(); + connection.bind(bindRequest(config.settings())); + ldapInterface = connection; + } + + SearchResultEntry searchResult = findUser(user, ldapInterface); + assert searchResult != null; + final String dn = searchResult.getDN(); + LdapSession session = new LdapSession(logger, ldapInterface, dn, groupResolver, timeout, searchResult.getAttributes()); + success = true; + return session; + } finally { + if (success == false && connection != null) { + connection.close(); + } } } - private String findUserDN(String user) throws Exception { - SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)) - , SearchRequest.NO_ATTRIBUTES); + private SearchResultEntry findUser(String user, LDAPInterface ldapInterface) throws Exception { + SearchRequest request = new SearchRequest(userSearchBaseDn, scope.scope(), createEqualityFilter(userAttribute, encodeValue(user)), + attributesToSearchFor(groupResolver.attributes())); request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - LDAPConnectionPool connectionPool = connectionPool(); - SearchResultEntry entry = searchForEntry(connectionPool, request, logger); + SearchResultEntry entry = searchForEntry(ldapInterface, request, logger); if (entry == null) { throw Exceptions.authenticationError("failed to find user [{}] with search base [{}] scope [{}]", user, userSearchBaseDn, scope.toString().toLowerCase(Locale.ENGLISH)); } - return entry.getDN(); - } - - private void tryBind(String dn, SecuredString password) throws IOException { - LDAPConnection bindConnection; - try { - bindConnection = serverSet.getConnection(); - } catch (LDAPException e) { - throw new IOException("unable to connect to any LDAP servers for bind", e); - } - - try { - bindConnection.bind(dn, new String(password.internalChars())); - } catch (LDAPException e) { - throw Exceptions.authenticationError("failed LDAP authentication for DN [{}]", e, dn); - } finally { - bindConnection.close(); - } + return entry; } /* - * This method is used to cleanup the connections for tests + * This method is used to cleanup the connections */ void shutdown() { if (connectionPool != null) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java index 12600548baf..52d668ec086 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolver.java @@ -12,15 +12,15 @@ import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResult; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; -import org.elasticsearch.xpack.security.support.Exceptions; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; @@ -29,20 +29,21 @@ import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.sear import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; /** -* -*/ + * Resolves the groups for a user by executing a search with a filter usually that contains a group object class with a attribute that + * matches an ID of the user + */ class SearchGroupsResolver implements GroupsResolver { private static final String GROUP_SEARCH_DEFAULT_FILTER = "(&" + - "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group))" + - "(|(uniqueMember={0})(member={0})))"; + "(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames)(objectclass=group)(objectclass=posixGroup))" + + "(|(uniqueMember={0})(member={0})(memberUid={0})))"; private final String baseDn; private final String filter; private final String userAttribute; private final LdapSearchScope scope; - public SearchGroupsResolver(Settings settings) { + SearchGroupsResolver(Settings settings) { baseDn = settings.get("base_dn"); if (baseDn == null) { throw new IllegalArgumentException("base_dn must be specified"); @@ -53,37 +54,60 @@ class SearchGroupsResolver implements GroupsResolver { } @Override - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - List groups = new LinkedList<>(); - - String userId = userAttribute != null ? readUserAttribute(connection, userDn, timeout, logger) : userDn; - try { - SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), createFilter(filter, userId), - SearchRequest.NO_ATTRIBUTES); - searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResult results = search(connection, searchRequest, logger); - for (SearchResultEntry entry : results.getSearchEntries()) { - groups.add(entry.getDN()); - } - } catch (LDAPException e) { - throw Exceptions.authenticationError("could not search for LDAP groups for DN [{}]", e, userDn); + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException { + String userId = getUserId(userDn, attributes, connection, timeout, logger); + if (userId == null) { + // attributes were queried but the requested wasn't found + return Collections.emptyList(); } + SearchRequest searchRequest = new SearchRequest(baseDn, scope.scope(), createFilter(filter, userId), + SearchRequest.NO_ATTRIBUTES); + searchRequest.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResult results = search(connection, searchRequest, logger); + List groups = new ArrayList<>(results.getSearchEntries().size()); + for (SearchResultEntry entry : results.getSearchEntries()) { + groups.add(entry.getDN()); + } return groups; } - String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - try { - SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); - request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResultEntry results = searchForEntry(connection, request, logger); - Attribute attribute = results.getAttribute(userAttribute); - if (attribute == null) { - throw Exceptions.authenticationError("no results returned for DN [{}] attribute [{}]", userDn, userAttribute); - } - return attribute.getValue(); - } catch (LDAPException e) { - throw Exceptions.authenticationError("could not retrieve attribute [{}] for DN [{}]", e, userAttribute, userDn); + public String[] attributes() { + if (userAttribute != null) { + return new String[] { userAttribute }; } + return null; + } + + private String getUserId(String dn, Collection attributes, LDAPInterface connection, TimeValue + timeout, ESLogger logger) throws LDAPException { + if (userAttribute == null) { + return dn; + } + + if (attributes != null) { + for (Attribute attribute : attributes) { + if (attribute.getName().equals(userAttribute)) { + return attribute.getValue(); + } + } + } + + return readUserAttribute(connection, dn, timeout, logger); + } + + String readUserAttribute(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) throws LDAPException { + SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, userAttribute); + request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResultEntry results = searchForEntry(connection, request, logger); + if (results == null) { + return null; + } + Attribute attribute = results.getAttribute(userAttribute); + if (attribute == null) { + return null; + } + return attribute.getValue(); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java index 9ae30969696..e04d9924063 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolver.java @@ -11,48 +11,64 @@ import com.unboundid.ldap.sdk.LDAPInterface; import com.unboundid.ldap.sdk.SearchRequest; import com.unboundid.ldap.sdk.SearchResultEntry; import com.unboundid.ldap.sdk.SearchScope; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSession.GroupsResolver; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.OBJECT_CLASS_PRESENCE_FILTER; import static org.elasticsearch.xpack.security.authc.ldap.support.LdapUtils.searchForEntry; /** -* +* Resolves the groups of a user based on the value of a attribute of the user's ldap entry */ class UserAttributeGroupsResolver implements GroupsResolver { private final String attribute; - public UserAttributeGroupsResolver(Settings settings) { + UserAttributeGroupsResolver(Settings settings) { this(settings.get("user_group_attribute", "memberOf")); } - public UserAttributeGroupsResolver(String attribute) { - this.attribute = attribute; + private UserAttributeGroupsResolver(String attribute) { + this.attribute = Objects.requireNonNull(attribute); } @Override - public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) { - try { - SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); - request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); - SearchResultEntry result = searchForEntry(connection, request, logger); - Attribute attributeReturned = result.getAttribute(attribute); - if (attributeReturned == null) { - return Collections.emptyList(); + public List resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException { + if (attributes != null) { + for (Attribute attribute : attributes) { + if (attribute.getName().equals(attribute)) { + String[] values = attribute.getValues(); + return Arrays.asList(values); + } } - String[] values = attributeReturned.getValues(); - return Arrays.asList(values); - } catch (LDAPException e) { - throw new ElasticsearchException("could not look up group attributes for DN [{}]", e, userDn); + return Collections.emptyList(); } + + SearchRequest request = new SearchRequest(userDn, SearchScope.BASE, OBJECT_CLASS_PRESENCE_FILTER, attribute); + request.setTimeLimitSeconds(Math.toIntExact(timeout.seconds())); + SearchResultEntry result = searchForEntry(connection, request, logger); + if (result == null) { + return Collections.emptyList(); + } + Attribute attributeReturned = result.getAttribute(attribute); + if (attributeReturned == null) { + return Collections.emptyList(); + } + String[] values = attributeReturned.getValues(); + return Arrays.asList(values); + } + + @Override + public String[] attributes() { + return new String[] { attribute }; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java index 4c542f44e8b..8165c8dce03 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/AbstractLdapRealm.java @@ -9,12 +9,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.unboundid.ldap.sdk.LDAPException; +import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; import org.elasticsearch.xpack.security.authc.support.RefreshListener; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; -import org.elasticsearch.xpack.security.user.User; /** * Supporting class for LDAP realms @@ -86,7 +87,7 @@ public abstract class AbstractLdapRealm extends CachingUsernamePasswordRealm { } } - private User createUser(String principal, LdapSession session) { + private User createUser(String principal, LdapSession session) throws LDAPException { List groupDNs = session.groups(); Set roles = roleMapper.resolveRoles(session.userDn(), groupDNs); return new User(principal, roles.toArray(new String[roles.size()])); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java index f6896d8498a..a41e28e7e86 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapSession.java @@ -5,12 +5,15 @@ */ package org.elasticsearch.xpack.security.authc.ldap.support; +import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPInterface; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.unit.TimeValue; import java.io.Closeable; +import java.util.Collection; import java.util.List; /** @@ -23,6 +26,7 @@ public class LdapSession implements Closeable { protected final String userDn; protected final GroupsResolver groupsResolver; protected final TimeValue timeout; + protected final Collection attributes; /** * This object is intended to be constructed by the LdapConnectionFactory @@ -32,12 +36,14 @@ public class LdapSession implements Closeable { * outside of and be reused across all connections. We can't keep a static logger in this class * since we want the logger to be contextual (i.e. aware of the settings and its environment). */ - public LdapSession(ESLogger logger, LDAPInterface connection, String userDn, GroupsResolver groupsResolver, TimeValue timeout) { + public LdapSession(ESLogger logger, LDAPInterface connection, String userDn, GroupsResolver groupsResolver, TimeValue timeout, + Collection attributes) { this.logger = logger; this.ldap = connection; this.userDn = userDn; this.groupsResolver = groupsResolver; this.timeout = timeout; + this.attributes = attributes; } /** @@ -61,13 +67,15 @@ public class LdapSession implements Closeable { /** * @return List of fully distinguished group names */ - public List groups() { - return groupsResolver.resolve(ldap, userDn, timeout, logger); + public List groups() throws LDAPException { + return groupsResolver.resolve(ldap, userDn, timeout, logger, attributes); } public interface GroupsResolver { - List resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger); + List resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger, + Collection attributes) throws LDAPException; + String[] attributes(); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java index a66964e4e76..f2ccb1b4fbb 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/LdapUtils.java @@ -46,7 +46,7 @@ public final class LdapUtils { /** * This method performs a LDAPConnection.search(...) operation while handling referral exceptions. This is necessary - * to maintain backwards compatibility + * to maintain backwards compatibility with the original JNDI implementation */ public static SearchResult search(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException { SearchResult results; @@ -68,7 +68,7 @@ public final class LdapUtils { /** * This method performs a LDAPConnection.searchForEntry(...) operation while handling referral exceptions. This is necessary - * to maintain backwards compatibility + * to maintain backwards compatibility with the original JNDI implementation */ public static SearchResultEntry searchForEntry(LDAPInterface ldap, SearchRequest searchRequest, ESLogger logger) throws LDAPException { SearchResultEntry entry; @@ -93,6 +93,10 @@ public final class LdapUtils { new StringBuffer(), null).toString()); } + public static String[] attributesToSearchFor(String[] attributes) { + return attributes == null ? new String[] { SearchRequest.NO_ATTRIBUTES } : attributes; + } + static String[] encodeFilterValues(String... arguments) { for (int i = 0; i < arguments.length; i++) { arguments[i] = Filter.encodeValue(arguments[i]); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java index e707247cbc1..cd8799486b7 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactory.java @@ -48,18 +48,16 @@ public abstract class SessionFactory { private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE); protected final ESLogger logger; - protected final ESLogger connectionLogger; protected final RealmConfig config; protected final TimeValue timeout; protected final ClientSSLService sslService; - protected ServerSet serverSet; - protected boolean sslUsed; + protected final ServerSet serverSet; + protected final boolean sslUsed; protected SessionFactory(RealmConfig config, ClientSSLService sslService) { this.config = config; this.logger = config.logger(getClass()); - this.connectionLogger = config.logger(getClass()); TimeValue searchTimeout = config.settings().getAsTime(TIMEOUT_LDAP_SETTING, TIMEOUT_DEFAULT); if (searchTimeout.millis() < 1000L) { logger.warn("ldap_search timeout [{}] is less than the minimum supported search timeout of 1s. using 1s", @@ -68,6 +66,9 @@ public abstract class SessionFactory { } this.timeout = searchTimeout; this.sslService = sslService; + LDAPServers ldapServers = ldapServers(config.settings()); + this.serverSet = serverSet(config.settings(), sslService, ldapServers); + this.sslUsed = ldapServers.ssl; } /** @@ -80,9 +81,6 @@ public abstract class SessionFactory { * @throws Exception if an error occurred when creating the session */ public final LdapSession session(String user, SecuredString password) throws Exception { - if (serverSet == null) { - throw new IllegalStateException("session factory is not initialized"); - } return getSession(user, password); } @@ -119,19 +117,11 @@ public abstract class SessionFactory { throw new UnsupportedOperationException("unauthenticated sessions are not supported"); } - public T init() { - LDAPServers ldapServers = ldapServers(config.settings()); - this.serverSet = serverSet(config.settings(), sslService, ldapServers); - this.sslUsed = ldapServers.ssl; - return (T) this; - } - protected static LDAPConnectionOptions connectionOptions(Settings settings) { LDAPConnectionOptions options = new LDAPConnectionOptions(); options.setConnectTimeoutMillis(Math.toIntExact(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis())); options.setFollowReferrals(settings.getAsBoolean(FOLLOW_REFERRALS_SETTING, true)); options.setResponseTimeoutMillis(settings.getAsTime(TIMEOUT_TCP_READ_SETTING, TIMEOUT_DEFAULT).millis()); - options.setAutoReconnect(true); options.setAllowConcurrentSocketFactoryUse(true); if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) { options.setSSLSocketVerifier(new HostNameSSLSocketVerifier(true)); @@ -139,15 +129,19 @@ public abstract class SessionFactory { return options; } - protected LDAPServers ldapServers(Settings settings) { + protected final LDAPServers ldapServers(Settings settings) { // Parse LDAP urls - String[] ldapUrls = settings.getAsArray(URLS_SETTING); + String[] ldapUrls = settings.getAsArray(URLS_SETTING, getDefaultLdapUrls(settings)); if (ldapUrls == null || ldapUrls.length == 0) { throw new IllegalArgumentException("missing required LDAP setting [" + URLS_SETTING + "]"); } return new LDAPServers(ldapUrls); } + protected String[] getDefaultLdapUrls(Settings settings) { + return null; + } + protected ServerSet serverSet(Settings settings, ClientSSLService clientSSLService, LDAPServers ldapServers) { SocketFactory socketFactory = null; if (ldapServers.ssl()) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/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/ssl/CertUtils.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java index 87ab1c528d3..5d85c447f68 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertUtils.java @@ -5,9 +5,13 @@ */ package org.elasticsearch.xpack.security.ssl; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; +import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.ExtensionsGenerator; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.Time; @@ -24,6 +28,8 @@ import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; import org.elasticsearch.common.SuppressForbidden; @@ -40,6 +46,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509ExtendedTrustManager; +import javax.security.auth.x500.X500Principal; import java.io.ByteArrayInputStream; import java.io.Reader; import java.math.BigInteger; @@ -60,6 +67,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.Supplier; class CertUtils { @@ -155,7 +163,7 @@ class CertUtils { } } - static PrivateKey readPrivateKey(Reader reader, char[] keyPassword) throws Exception { + static PrivateKey readPrivateKey(Reader reader, Supplier passwordSupplier) throws Exception { try (PEMParser parser = new PEMParser(reader)) { Object parsed; List list = new ArrayList<>(1); @@ -173,6 +181,7 @@ class CertUtils { PrivateKeyInfo privateKeyInfo; Object parsedObject = list.get(0); if (parsedObject instanceof PEMEncryptedKeyPair) { + char[] keyPassword = passwordSupplier.get(); if (keyPassword == null) { throw new IllegalArgumentException("cannot read encrypted key without a password"); } @@ -195,30 +204,65 @@ class CertUtils { } } - static X509Certificate generateSignedCertificate(boolean resolveHostname, String nodeName, Set addresses, KeyPair keyPair, - Certificate caCert, PrivateKey caPrivKey) throws Exception { + static X509Certificate generateCACertificate(X500Principal x500Principal, KeyPair keyPair) throws Exception { + return generateSignedCertificate(x500Principal, null, keyPair, null, null, true); + } + + static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair, + X509Certificate caCert, PrivateKey caPrivKey) throws Exception { + return generateSignedCertificate(principal, subjectAltNames, keyPair, caCert, caPrivKey, false); + } + + private static X509Certificate generateSignedCertificate(X500Principal principal, GeneralNames subjectAltNames, KeyPair keyPair, + X509Certificate caCert, PrivateKey caPrivKey, boolean ca) throws Exception { final DateTime notBefore = new DateTime(DateTimeZone.UTC); final DateTime notAfter = notBefore.plusYears(1); - final BigInteger serial = getSerial(); - - X509Certificate x509CACert = (X509Certificate) caCert; - X500Name subject = new X500Name("CN=" + nodeName); - JcaX509v3CertificateBuilder builder = - new JcaX509v3CertificateBuilder(X500Name.getInstance(x509CACert.getIssuerX500Principal().getEncoded()), serial, - new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic()); - + final BigInteger serial = CertUtils.getSerial(); JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); - builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic())); - builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(x509CACert)); - if (addresses.isEmpty() == false) { - builder.addExtension(Extension.subjectAlternativeName, false, getSubjectAlternativeNames(resolveHostname, addresses)); + + X500Name subject = X500Name.getInstance(principal.getEncoded()); + final X500Name issuer; + final AuthorityKeyIdentifier authorityKeyIdentifier; + if (caCert != null) { + if (caCert.getBasicConstraints() < 0) { + throw new IllegalArgumentException("ca certificate is not a CA!"); + } + issuer = X500Name.getInstance(caCert.getIssuerX500Principal().getEncoded()); + authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert); + } else { + issuer = subject; + authorityKeyIdentifier = + extUtils.createAuthorityKeyIdentifier(keyPair.getPublic(), new X500Principal(issuer.toString()), serial); } - ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(caPrivKey); + JcaX509v3CertificateBuilder builder = + new JcaX509v3CertificateBuilder(issuer, serial, + new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic()); + + builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic())); + builder.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier); + if (subjectAltNames != null) { + builder.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); + } + builder.addExtension(Extension.basicConstraints, ca, new BasicConstraints(ca)); + + PrivateKey signingKey = caPrivKey != null ? caPrivKey : keyPair.getPrivate(); + ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey); X509CertificateHolder certificateHolder = builder.build(signer); return new JcaX509CertificateConverter().getCertificate(certificateHolder); } + static PKCS10CertificationRequest generateCSR(KeyPair keyPair, X500Principal principal, GeneralNames sanList) throws Exception { + JcaPKCS10CertificationRequestBuilder builder = new JcaPKCS10CertificationRequestBuilder(principal, keyPair.getPublic()); + if (sanList != null) { + ExtensionsGenerator extGen = new ExtensionsGenerator(); + extGen.addExtension(Extension.subjectAlternativeName, false, sanList); + builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); + } + + return builder.build(new JcaContentSignerBuilder("SHA256withRSA").setProvider(CertUtils.BC_PROV).build(keyPair.getPrivate())); + } + static BigInteger getSerial() { SecureRandom random = new SecureRandom(); BigInteger serial = new BigInteger(SERIAL_BIT_LENGTH, random); @@ -226,10 +270,10 @@ class CertUtils { return serial; } - static KeyPair generateKeyPair() throws Exception { + static KeyPair generateKeyPair(int keysize) throws Exception { // generate a private key KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(2048); + keyPairGenerator.initialize(keysize); return keyPairGenerator.generateKeyPair(); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java new file mode 100644 index 00000000000..df1d35b4717 --- /dev/null +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/CertificateTool.java @@ -0,0 +1,627 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.ssl; + +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.bouncycastle.asn1.DERIA5String; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.openssl.PEMEncryptor; +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; +import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.cli.SettingCommand; +import org.elasticsearch.cli.Terminal; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.ParseFieldMatcher; +import org.elasticsearch.common.ParseFieldMatcherSupplier; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.env.Environment; +import org.elasticsearch.node.internal.InternalSettingsPreparer; +import org.elasticsearch.xpack.XPackPlugin; + +import javax.security.auth.x500.X500Principal; +import java.io.OutputStream; +import java.io.Reader; +import java.security.cert.Certificate; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * CLI tool to make generation of certificates or certificate requests easier for users + */ +public class CertificateTool extends SettingCommand { + + private static final String AUTO_GEN_CA_DN = "CN=Elastic Certificate Tool Autogenerated CA"; + private static final String DESCRIPTION = "Simplifies certificate creation for use with the Elastic Stack"; + private static final String DEFAULT_CSR_FILE = "csr-bundle.zip"; + private static final String DEFAULT_CERT_FILE = "certificate-bundle.zip"; + private static final Pattern ALLOWED_FILENAME_CHAR_PATTERN = Pattern.compile("([a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]+)"); + private static final int DEFAULT_KEY_SIZE = 2048; + private static final int FILE_EXTENSION_LENGTH = 4; + static final int MAX_FILENAME_LENGTH = 255 - FILE_EXTENSION_LENGTH; + private static final ObjectParser, CertInfoParseContext> PARSER = new ObjectParser<>("certgen"); + static { + ConstructingObjectParser instanceParser = + new ConstructingObjectParser<>("instances", + a -> new CertificateInformation((String) a[0], (List) a[1], (List) a[2])); + instanceParser.declareString(ConstructingObjectParser.constructorArg(), new ParseField("name")); + instanceParser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("ip")); + instanceParser.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), new ParseField("dns")); + + PARSER.declareObjectArray(List::addAll, instanceParser, new ParseField("instances")); + } + + private final OptionSpec outputPathSpec; + private final OptionSpec csrSpec; + private final OptionSpec caCertPathSpec; + private final OptionSpec caKeyPathSpec; + private final OptionSpec caPasswordSpec; + private final OptionSpec caDnSpec; + private final OptionSpec keysizeSpec; + private final OptionSpec inputFileSpec; + + CertificateTool() { + super(DESCRIPTION); + outputPathSpec = parser.accepts("out", "path of the zip file that the output should be written to") + .withRequiredArg(); + csrSpec = parser.accepts("csr", "only generate certificate signing requests"); + caCertPathSpec = parser.accepts("cert", "path to an existing ca certificate").availableUnless(csrSpec).withRequiredArg(); + caKeyPathSpec = parser.accepts("key", "path to an existing ca private key") + .availableIf(caCertPathSpec) + .requiredIf(caCertPathSpec) + .withRequiredArg(); + caPasswordSpec = parser.accepts("pass", "password for an existing ca private key or the generated ca private key") + .availableUnless(csrSpec) + .withOptionalArg(); + caDnSpec = parser.accepts("dn", "distinguished name to use for the generated ca. defaults to " + AUTO_GEN_CA_DN) + .availableUnless(caCertPathSpec) + .withRequiredArg(); + keysizeSpec = parser.accepts("keysize", "size in bits of RSA keys").withRequiredArg().ofType(Integer.class); + inputFileSpec = parser.accepts("in", "file containing details of the instances in yaml format").withRequiredArg(); + } + + public static void main(String[] args) throws Exception { + new CertificateTool().main(args, Terminal.DEFAULT); + } + + @Override + protected void execute(Terminal terminal, OptionSet options, Map settings) throws Exception { + Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings); + final boolean csrOnly = options.has(csrSpec); + printIntro(terminal, csrOnly); + final Path outputFile = getOutputFile(terminal, outputPathSpec.value(options), env, csrOnly ? DEFAULT_CSR_FILE : DEFAULT_CERT_FILE); + final String inputFile = inputFileSpec.value(options); + final int keysize = options.has(keysizeSpec) ? keysizeSpec.value(options) : DEFAULT_KEY_SIZE; + if (csrOnly) { + Collection certificateInformations = getCertificateInformationList(terminal, inputFile, env); + generateAndWriteCsrs(outputFile, certificateInformations, keysize); + } else { + final String dn = options.has(caDnSpec) ? caDnSpec.value(options) : AUTO_GEN_CA_DN; + final boolean prompt = options.has(caPasswordSpec); + final char[] keyPass = options.hasArgument(caPasswordSpec) ? caPasswordSpec.value(options).toCharArray() : null; + CAInfo caInfo = + getCAInfo(terminal, dn, caCertPathSpec.value(options), caKeyPathSpec.value(options), keyPass, prompt, env, keysize); + Collection certificateInformations = getCertificateInformationList(terminal, inputFile, env); + generateAndWriteSignedCertificates(outputFile, certificateInformations, caInfo, keysize); + } + printConclusion(terminal, csrOnly, outputFile); + } + + @Override + protected void printAdditionalHelp(Terminal terminal) { + terminal.println("Simplifies the generation of certificate signing requests and signed"); + terminal.println("certificates. The tool runs interactively unless the 'in' and 'out' parameters"); + terminal.println("are specified. In the interactive mode, the tool will prompt for required"); + terminal.println("values that have not been provided through the use of command line options."); + terminal.println(""); + } + + /** + * Checks for output file in the user specified options or prompts the user for the output file + * + * @param terminal terminal to communicate with a user + * @param outputPath user specified output file, may be {@code null} + * @param env the environment for this tool to resolve files with + * @return a {@link Path} to the output file + */ + static Path getOutputFile(Terminal terminal, String outputPath, Environment env, String defaultFilename) throws IOException { + Path file; + if (outputPath != null) { + file = XPackPlugin.resolveConfigFile(env, Strings.cleanPath(outputPath)); + } else { + file = XPackPlugin.resolveConfigFile(env, defaultFilename); + String input = terminal.readText("Please enter the desired output file [" + file + "]: "); + if (input.isEmpty() == false) { + file = XPackPlugin.resolveConfigFile(env, Strings.cleanPath(input)); + } + } + return file; + } + + /** + * This method handles the collection of information about each instance that is necessary to generate a certificate. The user may + * be prompted or the information can be gathered from a file + * @param terminal the terminal to use for user interaction + * @param inputFile an optional file that will be used to load the instance information + * @param env the environment for this tool to resolve files with + * @return a {@link Collection} of {@link CertificateInformation} that represents each instance + */ + static Collection getCertificateInformationList(Terminal terminal, String inputFile, Environment env) + throws Exception { + if (inputFile != null) { + return parseFile(XPackPlugin.resolveConfigFile(env, inputFile)); + } + Map map = new HashMap<>(); + boolean done = false; + while (done == false) { + String name = terminal.readText("Enter instance name: "); + if (name.isEmpty() == false) { + String ipAddresses = terminal.readText("Enter IP Addresses for instance (comma-separated if more than one) []: "); + String dnsNames = terminal.readText("Enter DNS names for instance (comma-separated if more than one) []: "); + List ipList = Arrays.asList(Strings.splitStringByCommaToArray(ipAddresses)); + List dnsList = Arrays.asList(Strings.splitStringByCommaToArray(dnsNames)); + + CertificateInformation information = new CertificateInformation(name, ipList, dnsList); + List validationErrors = information.validate(); + if (validationErrors.isEmpty()) { + if (map.containsKey(name)) { + terminal.println("Overwriting previously defined instance information [" + name + "]"); + } + map.put(name, information); + } else { + for (String validationError : validationErrors) { + terminal.println(validationError); + } + terminal.println("Skipping entry as invalid values were found"); + } + } else { + terminal.println("A name must be provided"); + } + + String exit = terminal.readText("Would you like to specify another instance? Press 'y' to continue entering instance " + + "information: "); + if ("y".equals(exit) == false) { + done = true; + } + } + return map.values(); + } + + /** + * Parses the input file to retrieve the certificate information + * @param file the file to parse + * @return a collection of certificate information + */ + static Collection parseFile(Path file) throws Exception { + try (Reader reader = Files.newBufferedReader(file)) { + XContentParser xContentParser = XContentType.YAML.xContent().createParser(reader); + return PARSER.parse(xContentParser, new ArrayList<>(), new CertInfoParseContext()); + } + } + + /** + * Generates certificate signing requests and writes them out to the specified file in zip format + * @param outputFile the file to write the output to. This file must not already exist + * @param certInfo the details to use in the certificate signing requests + */ + static void generateAndWriteCsrs(Path outputFile, Collection certInfo, int keysize) throws Exception { + fullyWriteFile(outputFile, (outputStream, pemWriter) -> { + for (CertificateInformation certificateInformation : certInfo) { + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + GeneralNames sanList = getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames); + PKCS10CertificationRequest csr = CertUtils.generateCSR(keyPair, certificateInformation.name.x500Principal, sanList); + + final String dirName = certificateInformation.name.filename + "/"; + ZipEntry zipEntry = new ZipEntry(dirName); + assert zipEntry.isDirectory(); + outputStream.putNextEntry(zipEntry); + + // write csr + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".csr")); + pemWriter.writeObject(csr); + pemWriter.flush(); + outputStream.closeEntry(); + + // write private key + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".key")); + pemWriter.writeObject(keyPair.getPrivate()); + pemWriter.flush(); + outputStream.closeEntry(); + } + }); + } + + /** + * Returns the CA certificate and private key that will be used to sign certificates. These may be specified by the user or + * automatically generated + * + * @param terminal the terminal to use for prompting the user + * @param dn the distinguished name to use for the CA + * @param caCertPath the path to the CA certificate or {@code null} if not provided + * @param caKeyPath the path to the CA private key or {@code null} if not provided + * @param keyPass the password to the private key. If not present and the key is encrypted the user will be prompted + * @param env the environment for this tool to resolve files with + * @return CA cert and private key + */ + static CAInfo getCAInfo(Terminal terminal, String dn, String caCertPath, String caKeyPath, char[] keyPass, boolean prompt, + Environment env, int keysize) throws Exception { + if (caCertPath != null) { + assert caKeyPath != null; + Certificate[] certificates = CertUtils.readCertificates(Collections.singletonList(caCertPath), env); + if (certificates.length != 1) { + throw new IllegalArgumentException("expected a single certificate in file [" + caCertPath + "] but found [" + + certificates.length + "]"); + } + Certificate caCert = certificates[0]; + PrivateKey privateKey = readPrivateKey(caKeyPath, keyPass, terminal, env, prompt); + return new CAInfo((X509Certificate) caCert, privateKey); + } + + // generate the CA keys and cert + X500Principal x500Principal = new X500Principal(dn); + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + Certificate caCert = CertUtils.generateCACertificate(x500Principal, keyPair); + final char[] password; + if (prompt) { + password = terminal.readSecret("Enter password for CA private key: "); + } else { + password = keyPass; + } + return new CAInfo((X509Certificate) caCert, keyPair.getPrivate(), true, password); + } + + /** + * Generates signed certificates in PEM format stored in a zip file + * @param outputFile the file that the certificates will be written to. This file must not exist + * @param certificateInformations details for creation of the certificates + * @param caInfo the CA information to sign the certificates with + */ + static void generateAndWriteSignedCertificates(Path outputFile, Collection certificateInformations, + CAInfo caInfo, int keysize) throws Exception { + fullyWriteFile(outputFile, (outputStream, pemWriter) -> { + // write out the CA info first if it was generated + writeCAInfoIfGenerated(outputStream, pemWriter, caInfo); + + for (CertificateInformation certificateInformation : certificateInformations) { + KeyPair keyPair = CertUtils.generateKeyPair(keysize); + Certificate certificate = CertUtils.generateSignedCertificate(certificateInformation.name.x500Principal, + getSubjectAlternativeNamesValue(certificateInformation.ipAddresses, certificateInformation.dnsNames), + keyPair, caInfo.caCert, caInfo.privateKey); + + final String dirName = certificateInformation.name.filename + "/"; + ZipEntry zipEntry = new ZipEntry(dirName); + assert zipEntry.isDirectory(); + outputStream.putNextEntry(zipEntry); + + // write cert + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".crt")); + pemWriter.writeObject(certificate); + pemWriter.flush(); + outputStream.closeEntry(); + + // write private key + outputStream.putNextEntry(new ZipEntry(dirName + certificateInformation.name.filename + ".key")); + pemWriter.writeObject(keyPair.getPrivate()); + pemWriter.flush(); + outputStream.closeEntry(); + } + }); + } + + /** + * This method handles the deletion of a file in the case of a partial write + * @param file the file that is being written to + * @param writer writes the contents of the file + */ + private static void fullyWriteFile(Path file, Writer writer) throws Exception { + boolean success = false; + try (OutputStream outputStream = Files.newOutputStream(file, StandardOpenOption.CREATE_NEW); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream, StandardCharsets.UTF_8); + JcaPEMWriter pemWriter = new JcaPEMWriter(new OutputStreamWriter(zipOutputStream, StandardCharsets.UTF_8))) { + writer.write(zipOutputStream, pemWriter); + success = true; + } finally { + if (success == false) { + Files.delete(file); + } + } + } + + /** + * This method handles writing out the certificate authority cert and private key if the certificate authority was generated by + * this invocation of the tool + * @param outputStream the output stream to write to + * @param pemWriter the writer for PEM objects + * @param info the certificate authority information + */ + private static void writeCAInfoIfGenerated(ZipOutputStream outputStream, JcaPEMWriter pemWriter, CAInfo info) throws Exception { + if (info.generated) { + final String caDirName = "ca/"; + ZipEntry zipEntry = new ZipEntry(caDirName); + assert zipEntry.isDirectory(); + outputStream.putNextEntry(zipEntry); + outputStream.putNextEntry(new ZipEntry(caDirName + "ca.crt")); + pemWriter.writeObject(info.caCert); + pemWriter.flush(); + outputStream.closeEntry(); + outputStream.putNextEntry(new ZipEntry(caDirName + "ca.key")); + if (info.password != null && info.password.length > 0) { + try { + PEMEncryptor encryptor = new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build(info.password); + pemWriter.writeObject(info.privateKey, encryptor); + } finally { + // we can safely nuke the password chars now + Arrays.fill(info.password, (char) 0); + } + } else { + pemWriter.writeObject(info.privateKey); + } + pemWriter.flush(); + outputStream.closeEntry(); + } + } + + private static void printIntro(Terminal terminal, boolean csr) { + terminal.println("This tool assists you in the generation of X.509 certificates and certificate"); + terminal.println("signing requests for use with SSL in the Elastic stack. Depending on the command"); + terminal.println("line option specified, you may be prompted for the following:"); + terminal.println(""); + terminal.println("* The path to the output file"); + if (csr) { + terminal.println(" * The output file is a zip file containing the certificate signing requests"); + terminal.println(" and private keys for each instance."); + } else { + terminal.println(" * The output file is a zip file containing the signed certificates and"); + terminal.println(" private keys for each instance. If a Certificate Authority was generated,"); + terminal.println(" the certificate and private key will also be included in the output file."); + } + terminal.println("* Information about each instance"); + terminal.println(" * An instance is any piece of the Elastic Stack that requires a SSL certificate."); + terminal.println(" Depending on your configuration, Elasticsearch, Logstash, Kibana, and Beats"); + terminal.println(" may all require a certificate and private key."); + terminal.println(" * The minimum required value for each instance is a name. This can simply be the"); + terminal.println(" hostname, which will be used as the Common Name of the certificate. A full"); + terminal.println(" distinguished name may also be used."); + terminal.println(" * IP addresses and DNS names are optional. Multiple values can be specified as a"); + terminal.println(" comma separated string. If no IP addresses or DNS names are provided, you may"); + terminal.println(" disable hostname verification in your SSL configuration."); + + if (csr == false) { + terminal.println("* Certificate Authority private key password"); + terminal.println(" * The password may be left empty if desired."); + } + terminal.println(""); + terminal.println("Let's get started..."); + terminal.println(""); + } + + private static void printConclusion(Terminal terminal, boolean csr, Path outputFile) { + if (csr) { + terminal.println("Certificate signing requests written to " + outputFile); + terminal.println(""); + terminal.println("This file should be properly secured as it contains the private keys for all"); + terminal.println("instances."); + terminal.println(""); + terminal.println("After unzipping the file, there will be a directory for each instance containing"); + terminal.println("the certificate signing request and the private key. Provide the certificate"); + terminal.println("signing requests to your certificate authority. Once you have received the"); + terminal.println("signed certificate, copy the signed certificate, key, and CA certificate to the"); + terminal.println("configuration directory of the Elastic product that they will be used for and"); + terminal.println("follow the SSL configuration instructions in the product guide."); + } else { + terminal.println("Certificates written to " + outputFile); + terminal.println(""); + terminal.println("This file should be properly secured as it contains the private keys for all"); + terminal.println("instances and the certificate authority."); + terminal.println(""); + terminal.println("After unzipping the file, there will be a directory for each instance containing"); + terminal.println("the certificate and private key. Copy the certificate, key, and CA certificate"); + terminal.println("to the configuration directory of the Elastic product that they will be used for"); + terminal.println("and follow the SSL configuration instructions in the product guide."); + terminal.println(""); + terminal.println("For client applications, you may only need to copy the CA certificate and"); + terminal.println("configure the client to trust this certificate."); + } + } + + /** + * Helper method to read a private key and support prompting of user for a key. To avoid passwords being placed as an argument we + * can prompt the user for their password if we encounter an encrypted key. + * @param path the path to the private key + * @param password the password provided by the user or {@code null} + * @param terminal the terminal to use for user interaction + * @param env the environment to resolve files from + * @param prompt whether to prompt the user or not + * @return the {@link PrivateKey} that was read from the file + */ + private static PrivateKey readPrivateKey(String path, char[] password, Terminal terminal, Environment env, boolean prompt) + throws Exception { + AtomicReference passwordReference = new AtomicReference<>(password); + try (Reader reader = Files.newBufferedReader(XPackPlugin.resolveConfigFile(env, path), StandardCharsets.UTF_8)) { + return CertUtils.readPrivateKey(reader, () -> { + if (password != null || prompt == false) { + return password; + } + char[] promptedValue = terminal.readSecret("Enter password for CA private key: "); + passwordReference.set(promptedValue); + return promptedValue; + }); + } finally { + if (passwordReference.get() != null) { + Arrays.fill(passwordReference.get(), (char) 0); + } + } + } + + private static GeneralNames getSubjectAlternativeNamesValue(List ipAddresses, List dnsNames) { + Set generalNameList = new HashSet<>(); + for (String ip : ipAddresses) { + generalNameList.add(new GeneralName(GeneralName.iPAddress, ip)); + } + + for (String dns : dnsNames) { + generalNameList.add(new GeneralName(GeneralName.dNSName, dns)); + } + + if (generalNameList.isEmpty()) { + return null; + } + return new GeneralNames(generalNameList.toArray(new GeneralName[0])); + } + + static class CertificateInformation { + final Name name; + final List ipAddresses; + final List dnsNames; + + CertificateInformation(String name, List ipAddresses, List dnsNames) { + this.name = Name.fromUserProvidedName(name); + this.ipAddresses = ipAddresses == null ? Collections.emptyList() : ipAddresses; + this.dnsNames = dnsNames == null ? Collections.emptyList() : dnsNames; + } + + List validate() { + List errors = new ArrayList<>(); + if (name.error != null) { + errors.add(name.error); + } + for (String ip : ipAddresses) { + if (InetAddresses.isInetAddress(ip) == false) { + errors.add("[" + ip + "] is not a valid IP address"); + } + } + for (String dnsName : dnsNames) { + if (DERIA5String.isIA5String(dnsName) == false) { + errors.add("[" + dnsName + "] is not a valid DNS name"); + } + } + return errors; + } + } + + static class Name { + + final String originalName; + final X500Principal x500Principal; + final String filename; + final String error; + + private Name(String name, X500Principal x500Principal, String filename, String error) { + this.originalName = name; + this.x500Principal = x500Principal; + this.filename = filename; + this.error = error; + } + + static Name fromUserProvidedName(String name) { + if ("ca".equals(name)) { + return new Name(name, null, null, "[ca] may not be used as an instance name"); + } + + final X500Principal principal; + try { + if (name.contains("=")) { + principal = new X500Principal(name); + } else { + principal = new X500Principal("CN=" + name); + } + } catch (IllegalArgumentException e) { + String error = "[" + name + "] could not be converted to a valid DN\n" + e.getMessage() + "\n" + + ExceptionsHelper.stackTrace(e); + return new Name(name, null, null, error); + } + + String filename = attemptToConvertFilename(Strings.cleanPath(name)); + if (filename == null) { + return new Name(name, principal, null, "could not convert [" + name + "] to a valid filename"); + } + return new Name(name, principal, filename, null); + } + + static String attemptToConvertFilename(String name) { + StringBuilder builder = new StringBuilder(); + Matcher matcher = ALLOWED_FILENAME_CHAR_PATTERN.matcher(name); + while (matcher.find()) { + builder.append(matcher.group(1)); + } + + if (builder.length() > MAX_FILENAME_LENGTH) { + return builder.substring(0, MAX_FILENAME_LENGTH); + } + + if (builder.length() > 0) { + return builder.toString(); + } + return null; + } + } + + static class CAInfo { + final X509Certificate caCert; + final PrivateKey privateKey; + final boolean generated; + final char[] password; + + CAInfo(X509Certificate caCert, PrivateKey privateKey) { + this(caCert, privateKey, false, null); + } + + CAInfo(X509Certificate caCert, PrivateKey privateKey, boolean generated, char[] password) { + this.caCert = caCert; + this.privateKey = privateKey; + this.generated = generated; + this.password = password; + } + } + + private static class CertInfoParseContext implements ParseFieldMatcherSupplier { + + private final ParseFieldMatcher parseFieldMatcher; + + CertInfoParseContext() { + this.parseFieldMatcher = new ParseFieldMatcher(true); + } + + @Override + public ParseFieldMatcher getParseFieldMatcher() { + return parseFieldMatcher; + } + } + + private interface Writer { + void write(ZipOutputStream zipOutputStream, JcaPEMWriter pemWriter) throws Exception; + } +} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java index 7603574e024..7a5f7c03c02 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/ssl/PEMKeyConfig.java @@ -19,6 +19,7 @@ import java.nio.file.Path; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; class PEMKeyConfig extends KeyConfig { @@ -48,9 +49,13 @@ class PEMKeyConfig extends KeyConfig { } PrivateKey readPrivateKey(Path keyPath) throws Exception { + char[] password = keyPassword == null ? null : keyPassword.toCharArray(); try (Reader reader = Files.newBufferedReader(keyPath, StandardCharsets.UTF_8)) { - char[] password = keyPassword == null ? null : keyPassword.toCharArray(); - return CertUtils.readPrivateKey(reader, password); + return CertUtils.readPrivateKey(reader, () -> password); + } finally { + if (password != null) { + Arrays.fill(password, (char) 0); + } } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/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/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/activedirectory/ActiveDirectoryGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java index b275c31b210..2a02e94af60 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryGroupsResolverTests.java @@ -31,7 +31,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("scope", LdapSearchScope.SUB_TREE) .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -48,7 +49,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("base_dn", "CN=Builtin, DC=ad, DC=test, DC=elasticsearch,DC=com") .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, hasItem(containsString("Users"))); } @@ -58,7 +60,8 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { .put("base_dn", "CN=Users, CN=Builtin, DC=ad, DC=test, DC=elasticsearch, DC=com") .build(); ActiveDirectoryGroupsResolver resolver = new ActiveDirectoryGroupsResolver(settings, "DC=ad,DC=test,DC=elasticsearch,DC=com"); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, + null); assertThat(groups, hasItem(containsString("CN=Users,CN=Builtin"))); } @@ -69,8 +72,9 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-32-545", //Default Users group "S-1-5-21-3510024162-210737641-214529065-513" //Default Domain Users group }; - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + final String dn = "CN=Jarvis, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } @@ -80,8 +84,9 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-32-545", //Default Users group "S-1-5-21-3510024162-210737641-214529065-513", //Default Domain Users group "S-1-5-21-3510024162-210737641-214529065-1117"}; //Gods group - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + final String dn = "CN=Odin, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com"; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } @@ -95,9 +100,10 @@ public class ActiveDirectoryGroupsResolverTests extends GroupsResolverTestCase { "S-1-5-21-3510024162-210737641-214529065-1108", //Geniuses "S-1-5-21-3510024162-210737641-214529065-1106", //SHIELD "S-1-5-21-3510024162-210737641-214529065-1105"};//Avengers - Filter query = ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, - "CN=Bruce Banner, CN=Users, DC=ad, DC=test, DC=elasticsearch, DC=com", TimeValue.timeValueSeconds(10), - NoOpLogger.INSTANCE); + + final String dn = BRUCE_BANNER_DN; + Filter query = + ActiveDirectoryGroupsResolver.buildGroupQuery(ldapConnection, dn, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); assertValidSidQuery(query, expectedSids); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java index 1c01653718a..e0c48302f72 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmTests.java @@ -110,7 +110,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateUserPrincipleName() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateUserPrincipleName", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -122,7 +122,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateSAMAccountName() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateSAMAccountName", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -144,7 +144,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachesSuccesfulAuthentications() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateCachesSuccesfulAuthentications", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -160,7 +160,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachingCanBeDisabled() throws Exception { Settings settings = settings(Settings.builder().put(CachingUsernamePasswordRealm.CACHE_TTL_SETTING, -1).build()); RealmConfig config = new RealmConfig("testAuthenticateCachingCanBeDisabled", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -176,7 +176,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { public void testAuthenticateCachingClearsCacheOnRoleMapperRefresh() throws Exception { Settings settings = settings(); RealmConfig config = new RealmConfig("testAuthenticateCachingClearsCacheOnRoleMapperRefresh", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null).init()); + ActiveDirectorySessionFactory sessionFactory = spy(new ActiveDirectorySessionFactory(config, null)); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -203,7 +203,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); @@ -217,7 +217,7 @@ public class ActiveDirectoryRealmTests extends ESTestCase { .put(DnRoleMapper.ROLE_MAPPING_FILE_SETTING, getDataPath("role_mapping.yml")) .build()); RealmConfig config = new RealmConfig("testRealmMapsGroupsToRoles", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, null); DnRoleMapper roleMapper = new DnRoleMapper(ActiveDirectoryRealm.TYPE, config, resourceWatcherService, null); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, roleMapper); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java index 633685400b7..4347c8e9bdb 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectoryRealmUsageTests.java @@ -29,7 +29,7 @@ public class ActiveDirectoryRealmUsageTests extends AbstractActiveDirectoryInteg .put("load_balance.type", loadBalanceType) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); ActiveDirectoryRealm realm = new ActiveDirectoryRealm(config, sessionFactory, mock(DnRoleMapper.class)); Map stats = realm.usageStats(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java index 78da9be72a1..5aa133a5482 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/activedirectory/ActiveDirectorySessionFactoryTests.java @@ -5,7 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.activedirectory; -import org.elasticsearch.ElasticsearchSecurityException; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.LdapSessionFactory; @@ -19,7 +19,7 @@ import org.elasticsearch.test.junit.annotations.Network; import java.io.IOException; import java.util.List; -import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; @@ -31,7 +31,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI @SuppressWarnings("unchecked") public void testAdAuth() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "ironman"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -49,29 +49,45 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI } } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/499") + public void testNetbiosAuth() throws Exception { + final String adUrl = randomFrom("ldap://54.213.145.20:3268", "ldaps://54.213.145.20:3269", AD_LDAP_URL); + RealmConfig config = new RealmConfig("ad-test", buildAdSettings(adUrl, AD_DOMAIN, false), globalSettings); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); + + String userName = "ades\\ironman"; + try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { + List groups = ldap.groups(); + assertThat(groups, containsInAnyOrder( + containsString("Geniuses"), + containsString("Billionaire"), + containsString("Playboy"), + containsString("Philanthropists"), + containsString("Avengers"), + containsString("SHIELD"), + containsString("CN=Users,CN=Builtin"), + containsString("Domain Users"), + containsString("Supers"))); + } + } + public void testTcpReadTimeout() throws Exception { Settings settings = Settings.builder() .put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false)) + .put("group_search.filter", "(objectClass=*)") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, false) .put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); - try (LdapSession ldap = sessionFactory.session("ironman", SecuredStringTests.build(PASSWORD))) { - // In certain cases we may have a successful bind, but a search should take longer and cause a timeout - ldap.groups(); - fail("The TCP connection should timeout before getting groups back"); - } catch (ElasticsearchSecurityException e) { - assertAuthenticationException(e); - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting")); - } + LDAPException expected = expectThrows(LDAPException.class, + () -> sessionFactory.session("ironman", SecuredStringTests.build(PASSWORD)).groups()); + assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } public void testAdAuthAvengers() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", }; for(String user: users) { @@ -86,7 +102,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -108,7 +124,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Bruce Banner, CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.BASE, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -134,7 +150,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .put(ActiveDirectorySessionFactory.AD_GROUP_SEARCH_SCOPE_SETTING, LdapSearchScope.BASE) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "hulk"; try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { @@ -149,7 +165,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //Login with the UserPrincipalName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -167,7 +183,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", LdapSearchScope.ONE_LEVEL, false); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //login with sAMAccountName String userDN = "CN=Erik Selvig,CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com"; @@ -191,7 +207,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI "(&(objectclass=user)(userPrincipalName={0}@ad.test.elasticsearch.com))") .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); //Login with the UserPrincipalName try (LdapSession ldap = sessionFactory.session("erik.selvig", SecuredStringTests.build(PASSWORD))) { @@ -217,7 +233,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .build(); } RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { @@ -243,7 +259,7 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .build(); } RealmConfig config = new RealmConfig("ad-as-ldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { @@ -259,14 +275,12 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI public void testAdAuthWithHostnameVerification() throws Exception { RealmConfig config = new RealmConfig("ad-test", buildAdSettings(AD_LDAP_URL, AD_DOMAIN, true), globalSettings); - ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService).init(); + ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, clientSSLService); String userName = "ironman"; - try (LdapSession ldap = sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))) { - fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any active directory servers")); - } + LDAPException expected = + expectThrows(LDAPException.class, () -> sessionFactory.session(userName, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } public void testStandardLdapHostnameVerification() throws Exception { @@ -277,14 +291,11 @@ public class ActiveDirectorySessionFactoryTests extends AbstractActiveDirectoryI .put(LdapSessionFactory.HOSTNAME_VERIFICATION_SETTING, true) .build(); RealmConfig config = new RealmConfig("ad-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "Bruce Banner"; - try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { - fail("Test active directory certificate does not have proper hostname/ip address for hostname verification"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java index 2c652d22679..15a01289b88 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java @@ -42,7 +42,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase { RoleDescriptor rd = new RoleDescriptor("rolename", cluster, ips, runAs); assertThat(ESNativeRealmMigrateTool.MigrateUserOrRoles.createRoleJson(rd), equalTo("{\"cluster\":[],\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," + - "\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[]}")); + "\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[],\"metadata\":{}}")); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 67dd7dcc19a..3d162918cfa 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -55,7 +55,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; /** - * Tests for the ESNativeUsersStore and ESNativeRolesStore + * Tests for the NativeUsersStore and NativeRolesStore */ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { @@ -144,12 +144,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { SecurityClient c = securityClient(); final List existingRoles = Arrays.asList(c.prepareGetRoles().get().roles()); final int existing = existingRoles.size(); + final Map metadata = Collections.singletonMap("key", (Object) randomAsciiOfLengthBetween(1, 10)); logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) + .metadata(metadata) .get(); logger.error("--> waiting for .security index"); ensureGreen(SecurityTemplateService.SECURITY_INDEX_NAME); @@ -158,6 +160,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { assertTrue("role should exist", resp.hasRoles()); RoleDescriptor testRole = resp.roles()[0]; assertNotNull(testRole); + assertThat(testRole.getMetadata().size(), is(1)); + assertThat(testRole.getMetadata().get("key"), is(metadata.get("key"))); c.preparePutRole("test_role2") .cluster("all", "none") diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/GroupsResolverTestCase.java index 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 1f90bc69610..a99d64373bf 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -66,7 +66,7 @@ public class LdapRealmTests extends LdapTestCase { String userTemplate = VALID_USER_TEMPLATE; Settings settings = buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -82,7 +82,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); User user = ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -98,7 +98,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -116,7 +116,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); DnRoleMapper roleMapper = buildGroupAsRoleMapper(resourceWatcherService); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, roleMapper); @@ -143,7 +143,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); ldapFactory = spy(ldapFactory); LdapRealm ldap = new LdapRealm(config, ldapFactory, buildGroupAsRoleMapper(resourceWatcherService)); ldap.authenticate(new UsernamePasswordToken(VALID_USERNAME, SecuredStringTests.build(PASSWORD))); @@ -216,7 +216,7 @@ public class LdapRealmTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("test-ldap-realm-userdn", settings, globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm ldap = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null)); User user = ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", SecuredStringTests.build(PASSWORD))); @@ -244,7 +244,7 @@ public class LdapRealmTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap-realm", settings.build(), globalSettings); - LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFactory = new LdapSessionFactory(config, null); LdapRealm realm = new LdapRealm(config, ldapFactory, new DnRoleMapper(LdapRealm.TYPE, config, resourceWatcherService, null)); Map stats = realm.usageStats(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java index f4550255b1a..c6c161d9ec4 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapSessionFactoryTests.java @@ -6,8 +6,8 @@ package org.elasticsearch.xpack.security.authc.ldap; import com.unboundid.ldap.listener.InMemoryDirectoryServer; +import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPURL; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.security.authc.RealmConfig; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -19,9 +19,9 @@ import org.elasticsearch.xpack.security.authc.support.SecuredStringTests; import org.elasticsearch.test.junit.annotations.Network; import org.junit.Before; -import java.io.IOException; import java.util.List; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; @@ -49,7 +49,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -57,15 +57,15 @@ public class LdapSessionFactoryTests extends LdapTestCase { try (LdapSession session = sessionFactory.session(user, userPass)) { fail("expected connection timeout error here"); } catch (Exception e) { - assertThat(e, instanceOf(ElasticsearchSecurityException.class)); - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting ")); + assertThat(e, instanceOf(LDAPException.class)); + assertThat(e.getMessage(), containsString("A client-side timeout was encountered while waiting ")); } finally { ldapServer.setProcessingDelayMillis(0L); } } @Network - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch-shield/issues/767") + @AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2849") public void testConnectTimeout() { // Local sockets connect too fast... String ldapUrl = "ldap://54.200.235.244:389"; @@ -78,19 +78,17 @@ public class LdapSessionFactoryTests extends LdapTestCase { .build(); RealmConfig config = new RealmConfig("ldap_realm", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); long start = System.currentTimeMillis(); - try (LdapSession session = sessionFactory.session(user, userPass)) { - fail("expected connection timeout error here"); - } catch (Exception e) { - long time = System.currentTimeMillis() - start; - assertThat(time, lessThan(10000L)); - assertThat(e, instanceOf(IOException.class)); - assertThat(e.getCause().getCause().getMessage(), containsString("within the configured timeout of")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, userPass)); + long time = System.currentTimeMillis() - start; + assertThat(time, lessThan(10000L)); + assertThat(expected, instanceOf(LDAPException.class)); + assertThat(expected.getCause().getMessage(), + anyOf(containsString("within the configured timeout of"), containsString("connect timed out"))); } public void testBindWithTemplates() throws Exception { @@ -103,7 +101,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -124,15 +122,14 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplates, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); - try (LdapSession ldapConnection = ldapFac.session(user, userPass)) { - fail("Expected ElasticsearchSecurityException"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), is("failed LDAP authentication")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> ldapFac.session(user, userPass)); + assertThat(expected.getMessage(), containsString("Unable to bind as user")); + Throwable[] suppressed = expected.getSuppressed(); + assertThat(suppressed.length, is(2)); } public void testGroupLookupSubtree() throws Exception { @@ -141,7 +138,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); @@ -158,7 +155,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; try (LdapSession ldap = ldapFac.session(user, SecuredStringTests.build("pass"))) { @@ -173,7 +170,7 @@ public class LdapSessionFactoryTests extends LdapTestCase { RealmConfig config = new RealmConfig("ldap_realm", buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.BASE), globalSettings); - LdapSessionFactory ldapFac = new LdapSessionFactory(config, null).init(); + LdapSessionFactory ldapFac = new LdapSessionFactory(config, null); String user = "Horatio Hornblower"; SecuredString userPass = SecuredStringTests.build("pass"); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapUserSearchSessionFactoryTests.java index 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..66491145d82 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/OpenLdapTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authc.ldap; +import com.unboundid.ldap.sdk.LDAPException; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; @@ -25,6 +26,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Map; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasItem; @@ -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,29 @@ public class OpenLdapTests extends ESTestCase { .put("group_search.user_attribute", "uid") .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); try (LdapSession ldap = sessionFactory.session("selvig", SecuredStringTests.build(PASSWORD))){ assertThat(ldap.groups(), hasItem(containsString("Geniuses"))); } } - @AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/499") + @AwaitsFix(bugUrl = "https://github.com/elastic/x-plugins/issues/2849") public void testTcpTimeout() throws Exception { String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"; Settings settings = Settings.builder() - .put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.ONE_LEVEL)) + .put(buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE)) + .put("group_search.filter", "(objectClass=*)") .put(SessionFactory.HOSTNAME_VERIFICATION_SETTING, false) .put(SessionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); - try (LdapSession ldap = sessionFactory.session("thor", SecuredStringTests.build(PASSWORD))) { - // In certain cases we may have a successful bind, but a search should take longer and cause a timeout - ldap.groups(); - fail("The TCP connection should timeout before getting groups back"); - } catch (ElasticsearchException e) { - assertThat(e.getCause().getMessage(), containsString("A client-side timeout was encountered while waiting")); - } + LDAPException expected = expectThrows(LDAPException.class, + () -> sessionFactory.session("thor", SecuredStringTests.build(PASSWORD)).groups()); + assertThat(expected.getMessage(), containsString("A client-side timeout was encountered while waiting")); } public void testStandardLdapConnectionHostnameVerification() throws Exception { @@ -171,14 +170,11 @@ public class OpenLdapTests extends ESTestCase { .build(); RealmConfig config = new RealmConfig("oldap-test", settings, globalSettings); - LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService).init(); + LdapSessionFactory sessionFactory = new LdapSessionFactory(config, clientSSLService); String user = "blackwidow"; - try (LdapSession ldap = sessionFactory.session(user, SecuredStringTests.build(PASSWORD))) { - fail("OpenLDAP certificate does not contain the correct hostname/ip so hostname verification should fail on open"); - } catch (IOException e) { - assertThat(e.getMessage(), containsString("failed to connect to any LDAP servers")); - } + LDAPException expected = expectThrows(LDAPException.class, () -> sessionFactory.session(user, SecuredStringTests.build(PASSWORD))); + assertThat(expected.getMessage(), anyOf(containsString("Hostname verification failed"), containsString("peer not authenticated"))); } Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, LdapSearchScope scope) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java index df0ec6d60a0..7a0c638a5c8 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/SearchGroupsResolverTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.authc.ldap; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.authc.ldap.support.LdapSearchScope; @@ -31,7 +30,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -46,7 +45,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -61,7 +60,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Avengers"))); } @@ -74,7 +73,19 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { SearchGroupsResolver resolver = new SearchGroupsResolver(settings); List groups = resolver.resolve(ldapConnection, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", - TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE); + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); + assertThat(groups, hasItem(containsString("Geniuses"))); + } + + public void testFilterIncludesPosixGroups() throws Exception { + Settings settings = Settings.builder() + .put("base_dn", "dc=oldap,dc=test,dc=elasticsearch,dc=com") + .put("user_attribute", "uid") + .build(); + + SearchGroupsResolver resolver = new SearchGroupsResolver(settings); + List groups = resolver.resolve(ldapConnection, "uid=selvig,ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com", + TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE, null); assertThat(groups, hasItem(containsString("Geniuses"))); } @@ -116,12 +127,7 @@ public class SearchGroupsResolverTests extends GroupsResolverTestCase { .put("user_attribute", "doesntExists") .build(); SearchGroupsResolver resolver = new SearchGroupsResolver(settings); - try { - resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE); - fail("searching for a non-existing attribute should throw an LdapException"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), containsString("no results returned")); - } + assertNull(resolver.readUserAttribute(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(5), NoOpLogger.INSTANCE)); } public void testReadBinaryUserAttribute() throws Exception { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java index f297c920e7e..97a43ad228e 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/UserAttributeGroupsResolverTests.java @@ -26,7 +26,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { public void testResolve() throws Exception { //falling back on the 'memberOf' attribute UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(Settings.EMPTY); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, containsInAnyOrder( containsString("Avengers"), containsString("SHIELD"), @@ -39,7 +39,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { .put("user_group_attribute", "seeAlso") .build(); UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, hasItems(containsString("Avengers"))); //seeAlso only has Avengers } @@ -48,7 +48,7 @@ public class UserAttributeGroupsResolverTests extends GroupsResolverTestCase { .put("user_group_attribute", "doesntExist") .build(); UserAttributeGroupsResolver resolver = new UserAttributeGroupsResolver(settings); - List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE); + List groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null); assertThat(groups, empty()); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java index 2201bff81f7..3e9f855e689 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryLoadBalancingTests.java @@ -166,7 +166,7 @@ public class SessionFactoryLoadBalancingTests extends LdapTestCase { LdapSearchScope.SUB_TREE, loadBalancing); RealmConfig config = new RealmConfig("test-session-factory", settings, Settings.builder().put("path.home", createTempDir()).build()); - return new TestSessionFactory(config, null).init(); + return new TestSessionFactory(config, null); } static class TestSessionFactory extends SessionFactory { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java index 5d6a87f88dc..cf78c0d5302 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/support/SessionFactoryTests.java @@ -63,6 +63,6 @@ public class SessionFactoryTests extends ESTestCase { protected LdapSession getSession(String user, SecuredString password) { return null; } - }.init(); + }; } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiAuthenticationTests.java index 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/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/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/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); } } 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/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/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/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" } 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 1203ccb16a1..f078d1fb1c9 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/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)); + } +} 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..7e7778caf17 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.Template; -import org.elasticsearch.xpack.common.ScriptServiceProxy; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.ScriptService; +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) { @@ -79,11 +80,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/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..7e441287762 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.script.Script; 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,9 @@ 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"))) - .thenReturn(compiledScript); - when(proxy.executable(compiledScript, model)).thenReturn(script); + when(service.compile(new Script(templateText, type, lang, merged), WatcherScript.CTX, + Collections.singletonMap("content_type", "text/plain"))).thenReturn(compiledScript); + when(service.executable(compiledScript, model)).thenReturn(script); when(script.run()).thenReturn("rendered_text"); TextTemplate template = templateBuilder(type, templateText).params(params).build(); @@ -75,10 +76,9 @@ 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), - Collections.singletonMap("content_type", "text/plain"))) - .thenReturn(compiledScript); - when(proxy.executable(compiledScript, model)).thenReturn(script); + when(service.compile(new Script(templateText, scriptType, lang, model), WatcherScript.CTX, + Collections.singletonMap("content_type", "text/plain"))).thenReturn(compiledScript); + when(service.executable(compiledScript, model)).thenReturn(script); when(script.run()).thenReturn("rendered_text"); TextTemplate template = templateBuilder(scriptType, templateText).params(params).build(); @@ -90,10 +90,9 @@ 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), - Collections.singletonMap("content_type", "text/plain"))) - .thenReturn(compiledScript); - when(proxy.executable(compiledScript, model)).thenReturn(script); + when(service.compile(new Script(templateText, ScriptType.INLINE, lang, model), WatcherScript.CTX, + Collections.singletonMap("content_type", "text/plain"))).thenReturn(compiledScript); + 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/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` 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/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/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 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))), diff --git a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/support/WatcherUtilsTests.java b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/support/WatcherUtilsTests.java index 74536aa1e18..8d322064578 100644 --- a/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/support/WatcherUtilsTests.java +++ b/elasticsearch/x-pack/watcher/src/test/java/org/elasticsearch/xpack/watcher/support/WatcherUtilsTests.java @@ -98,7 +98,7 @@ public class WatcherUtilsTests extends ESTestCase { public void testSerializeSearchRequest() throws Exception { String[] randomIndices = generateRandomStringArray(5, 5, false); SearchRequest expectedRequest = new SearchRequest(randomIndices); - Script expectedTemplate = null; + WatcherScript expectedTemplate = null; if (randomBoolean()) { String[] randomTypes = generateRandomStringArray(2, 5, false); @@ -121,7 +121,8 @@ public class WatcherUtilsTests extends ESTestCase { } } String text = randomAsciiOfLengthBetween(1, 5); - expectedTemplate = randomFrom(Script.inline(text), Script.file(text), Script.indexed(text)).params(params).build(); + expectedTemplate = randomFrom(WatcherScript.inline(text), WatcherScript.file(text), + WatcherScript.indexed(text)).params(params).build(); } WatcherSearchTemplateRequest request = new WatcherSearchTemplateRequest(expectedRequest, expectedTemplate); @@ -199,7 +200,7 @@ public class WatcherUtilsTests extends ESTestCase { source = searchSourceBuilder.buildAsBytes(XContentType.JSON); builder.rawField("body", source); } - Script template = null; + WatcherScript template = null; if (randomBoolean()) { Map 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))); } }