Merge branch 'master' into deguice8

Original commit: elastic/x-pack-elasticsearch@8b273d3f8a
This commit is contained in:
Ryan Ernst 2016-07-18 13:54:43 -07:00
commit e2303f2584
109 changed files with 2526 additions and 974 deletions

View File

@ -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"));

View File

@ -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;",

View File

@ -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);
}
}

View File

@ -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<Class<? extends Plugin>> 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));

View File

@ -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));

View File

@ -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()

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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;
}
}
}

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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;
}
}
}

View File

@ -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"));

View File

@ -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);
}
}

View File

@ -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");

View File

@ -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");

View File

@ -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 {

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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`

View File

@ -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`

View File

@ -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`

View File

@ -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);

View File

@ -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<PutRoleRequest> implements Wri
private List<RoleDescriptor.IndicesPrivileges> indicesPrivileges = new ArrayList<>();
private String[] runAs = Strings.EMPTY_ARRAY;
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
private Map<String, Object> metadata;
public PutRoleRequest() {
}
@ -43,6 +45,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> 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<PutRoleRequest> implements Wri
return refreshPolicy;
}
public void metadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
public String name() {
return name;
}
@ -102,6 +112,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
return runAs;
}
public Map<String, Object> metadata() {
return metadata;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
@ -114,6 +128,7 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
}
runAs = in.readStringArray();
refreshPolicy = RefreshPolicy.readFrom(in);
metadata = in.readMap();
}
@Override
@ -127,12 +142,14 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> 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);
}
}

View File

@ -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<PutRoleRequest,
request.cluster(descriptor.getClusterPrivileges());
request.addIndex(descriptor.getIndicesPrivileges());
request.runAs(descriptor.getRunAs());
request.metadata(descriptor.getMetadata());
return this;
}
@ -56,4 +59,9 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
request.addIndex(indices, privileges, fields, query);
return this;
}
public PutRoleRequestBuilder metadata(Map<String, Object> metadata) {
request.metadata(metadata);
return this;
}
}

View File

@ -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<String, Integer> pair : hostPortPairs) {
try {
transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(pair.v1()), pair.v2()));

View File

@ -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<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
@Override
public List<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger,
Collection<Attribute> 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<String> 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<Filter> 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;
}
}

View File

@ -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));
}

View File

@ -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 <code>DOMAIN\\username</code> down-level credentials and this class contains the logic necessary
* to authenticate this form of a username
*/
static class DownLevelADAuthenticator extends ADAuthenticator {
Cache<String, String> domainNameCache = CacheBuilder.<String, String>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()));
}
}
}

View File

@ -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) {

View File

@ -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;
}
/**

View File

@ -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) {

View File

@ -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<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger) {
List<String> 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<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger,
Collection<Attribute> 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<String> 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<Attribute> 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();
}
}

View File

@ -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<String> 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<String> resolve(LDAPInterface connection, String userDn, TimeValue timeout, ESLogger logger,
Collection<Attribute> 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 };
}
}

View File

@ -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<String> groupDNs = session.groups();
Set<String> roles = roleMapper.resolveRoles(session.userDn(), groupDNs);
return new User(principal, roles.toArray(new String[roles.size()]));

View File

@ -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<Attribute> 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<Attribute> 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<String> groups() {
return groupsResolver.resolve(ldap, userDn, timeout, logger);
public List<String> groups() throws LDAPException {
return groupsResolver.resolve(ldap, userDn, timeout, logger, attributes);
}
public interface GroupsResolver {
List<String> resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger);
List<String> resolve(LDAPInterface ldapConnection, String userDn, TimeValue timeout, ESLogger logger,
Collection<Attribute> attributes) throws LDAPException;
String[] attributes();
}
}

View File

@ -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]);

View File

@ -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 extends SessionFactory> 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()) {

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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");
}
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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() {

View File

@ -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() {

View File

@ -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<char[]> passwordSupplier) throws Exception {
try (PEMParser parser = new PEMParser(reader)) {
Object parsed;
List<Object> 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<InetAddress> 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();
}

View File

@ -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<List<CertificateInformation>, CertInfoParseContext> PARSER = new ObjectParser<>("certgen");
static {
ConstructingObjectParser<CertificateInformation, CertInfoParseContext> instanceParser =
new ConstructingObjectParser<>("instances",
a -> new CertificateInformation((String) a[0], (List<String>) a[1], (List<String>) 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<String> outputPathSpec;
private final OptionSpec<Void> csrSpec;
private final OptionSpec<String> caCertPathSpec;
private final OptionSpec<String> caKeyPathSpec;
private final OptionSpec<String> caPasswordSpec;
private final OptionSpec<String> caDnSpec;
private final OptionSpec<Integer> keysizeSpec;
private final OptionSpec<String> 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<String, String> 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<CertificateInformation> 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<CertificateInformation> 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<CertificateInformation> getCertificateInformationList(Terminal terminal, String inputFile, Environment env)
throws Exception {
if (inputFile != null) {
return parseFile(XPackPlugin.resolveConfigFile(env, inputFile));
}
Map<String, CertificateInformation> 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<String> ipList = Arrays.asList(Strings.splitStringByCommaToArray(ipAddresses));
List<String> dnsList = Arrays.asList(Strings.splitStringByCommaToArray(dnsNames));
CertificateInformation information = new CertificateInformation(name, ipList, dnsList);
List<String> 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<CertificateInformation> 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<CertificateInformation> 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<CertificateInformation> 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<char[]> 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<String> ipAddresses, List<String> dnsNames) {
Set<GeneralName> 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<String> ipAddresses;
final List<String> dnsNames;
CertificateInformation(String name, List<String> ipAddresses, List<String> dnsNames) {
this.name = Name.fromUserProvidedName(name);
this.ipAddresses = ipAddresses == null ? Collections.emptyList() : ipAddresses;
this.dnsNames = dnsNames == null ? Collections.emptyList() : dnsNames;
}
List<String> validate() {
List<String> 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;
}
}

View File

@ -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);
}
}
}

View File

@ -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<String, Object> 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<String, Object> entry : ((Map<String, Object>)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<String, Object> 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<String, Object> metadata) {
for (String key : metadata.keySet()) {
if (key.startsWith(RESERVED_PREFIX)) {
return true;
}
}
return false;
}
}

View File

@ -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<String, Object> 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<String, Object> metadata) {
void verifyNoReservedMetadata(Map<String, Object> 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<String, Object> entry : ((Map<String, Object>)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);
}
}

View File

@ -86,6 +86,10 @@
},
"run_as" : {
"type" : "keyword"
},
"metadata" : {
"type" : "object",
"dynamic" : true
}
}
},

View File

@ -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!");

View File

@ -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){

View File

@ -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);
}
}

View File

@ -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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE);
List<String> 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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE);
List<String> 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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE);
List<String> 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);
}
}

View File

@ -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);

View File

@ -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<String, Object> stats = realm.usageStats();

View File

@ -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<String> 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) {

View File

@ -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\":{}}"));
}

View File

@ -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<RoleDescriptor> existingRoles = Arrays.asList(c.prepareGetRoles().get().roles());
final int existing = existingRoles.size();
final Map<String, Object> 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")

View File

@ -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());

View File

@ -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<String, Object> stats = realm.usageStats();

View File

@ -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");

View File

@ -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<String, String> 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;
}
}
}

View File

@ -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<String, Object> 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) {

View File

@ -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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE);
List<String> 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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE);
List<String> 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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(10), NoOpLogger.INSTANCE);
List<String> 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<String> 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<String> 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 {

View File

@ -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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE);
List<String> 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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE);
List<String> 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<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE);
List<String> groups = resolver.resolve(ldapConnection, BRUCE_BANNER_DN, TimeValue.timeValueSeconds(20), NoOpLogger.INSTANCE, null);
assertThat(groups, empty());
}

View File

@ -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 {

View File

@ -63,6 +63,6 @@ public class SessionFactoryTests extends ESTestCase {
protected LdapSession getSession(String user, SecuredString password) {
return null;
}
}.init();
};
}
}

View File

@ -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() {

View File

@ -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);
}

View File

@ -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<String, Object> 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<String, Object> 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);
}
}

View File

@ -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));

View File

@ -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<String, Map<String, String>> instanceInput = new HashMap<>(numberOfInstances);
for (int i = 0; i < numberOfInstances; i++) {
final String name = randomAsciiOfLengthBetween(1, 32);
Map<String, String> 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<String, Map<String, String>> 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<CertificateInformation> 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<String, String> instanceInfo = instanceInput.get(name);
assertNotNull("did not find map for " + name, instanceInfo);
List<String> expectedIps = Arrays.asList(Strings.commaDelimitedListToStringArray(instanceInfo.get("ip")));
List<String> 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<CertificateInformation> certInfos = CertificateTool.parseFile(instanceFile);
assertEquals(4, certInfos.size());
Map<String, CertificateInformation> 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<CertificateInformation> 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<CertificateInformation> 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<Certificate> 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());
}
}
}
}

View File

@ -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)) {

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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");

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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<String, Object> validMetadata = Collections.singletonMap("foo", "bar");
Map<String, Object> invalidMetadata = Collections.singletonMap(User.RESERVED_PREFIX + "foo", "bar");
Map<String, Object> 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));

View File

@ -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"

View File

@ -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" }

View File

@ -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

View File

@ -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<? extends Plugin>... plugins) {
this(settings, Arrays.asList(plugins));
}
public XPackTransportClient(Settings settings, Collection<Class<? extends Plugin>> plugins) {
super(settings, Settings.EMPTY, addPlugins(plugins, XPackPlugin.class));
}
}

View File

@ -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<String, String> compileParams) {
return securityContext.executeAs(XPackUser.INSTANCE, () ->
service.compile(script, WatcherScriptContext.CTX, compileParams));
}
public ExecutableScript executable(CompiledScript compiledScript, Map<String, Object> 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);
}
}

View File

@ -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<String, String> 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<String, Object> model) {
private Script convert(TextTemplate textTemplate, Map<String, Object> model) {
Map<String, Object> 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<String, String> compileParams(XContentType contentType) {

View File

@ -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);
}

View File

@ -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<String, Object> 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);

View File

@ -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`

View File

@ -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);
}

View File

@ -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<ScriptCondition, ScriptCondition.Result> {
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);

View File

@ -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<ScriptCondition> {
private final Script script;
private final WatcherScript script;
private Builder(Script script) {
private Builder(WatcherScript script) {
this.script = script;
}

View File

@ -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<ScriptCondition, ScriptCondition.Result, ExecutableScriptCondition> {
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;
}

View File

@ -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<HttpInput, HttpInput.Re
return new HttpInput.Result(request, -1, payload);
}
XContentType contentType = response.xContentType();
if (input.getExpectedResponseXContentType() != null) {
if (contentType != input.getExpectedResponseXContentType().contentType()) {
logger.warn("[{}] [{}] input expected content type [{}] but read [{}] from headers", type(), ctx.id(),
input.getExpectedResponseXContentType(), contentType);
}
if (contentType == null) {
contentType = input.getExpectedResponseXContentType().contentType();
}
final XContentType contentType;
XContentType responseContentType = response.xContentType();
if (input.getExpectedResponseXContentType() == null) {
//Attempt to auto detect content type, if not set in response
contentType = responseContentType != null ? responseContentType : XContentFactory.xContentType(response.body());
} else {
//Attempt to auto detect content type
if (contentType == null) {
contentType = XContentFactory.xContentType(response.body());
}
}
XContentParser parser = null;
if (contentType != null) {
try {
parser = contentType.xContent().createParser(response.body());
} 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());
contentType = input.getExpectedResponseXContentType().contentType();
if (responseContentType != contentType) {
logger.warn("[{}] [{}] input expected content type [{}] but read [{}] from headers, using expected one", type(), ctx.id(),
input.getExpectedResponseXContentType(), responseContentType);
}
}
final Map<String, Object> 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);
}

View File

@ -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<SearchInput, SearchInput.Re
@Inject
public SearchInputFactory(Settings settings, InternalClient client, IndicesQueriesRegistry queryRegistry,
AggregatorParsers aggParsers, Suggesters suggesters, ScriptServiceProxy scriptService) {
AggregatorParsers aggParsers, Suggesters suggesters, ScriptService scriptService) {
this(settings, new WatcherClientProxy(settings, client), queryRegistry, aggParsers, suggesters, scriptService);
}
public SearchInputFactory(Settings settings, WatcherClientProxy client, IndicesQueriesRegistry queryRegistry,
AggregatorParsers aggParsers, Suggesters suggesters, ScriptServiceProxy scriptService) {
AggregatorParsers aggParsers, Suggesters suggesters, ScriptService scriptService) {
super(Loggers.getLogger(ExecutableSimpleInput.class, settings));
this.parseFieldMatcher = new ParseFieldMatcher(settings);
this.client = client;

View File

@ -12,6 +12,8 @@ import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptService.ScriptType;
import org.elasticsearch.script.ScriptSettings;
@ -22,20 +24,22 @@ import java.util.Map;
/**
*
*/
public class Script implements ToXContent {
public class WatcherScript implements ToXContent {
public static final String DEFAULT_LANG = ScriptSettings.DEFAULT_LANG;
public static final ScriptContext.Plugin CTX_PLUGIN = new ScriptContext.Plugin("xpack", "watch");
public static final ScriptContext CTX = new WatcherScriptContext();
private final String script;
@Nullable private final ScriptType type;
@Nullable private final String lang;
@Nullable private final Map<String, Object> params;
Script(String script) {
WatcherScript(String script) {
this(script, null, null, null);
}
Script(String script, @Nullable ScriptType type, @Nullable String lang, @Nullable Map<String, Object> params) {
WatcherScript(String script, @Nullable ScriptType type, @Nullable String lang, @Nullable Map<String, Object> 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<Inline> {
@ -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();
}
}
}

View File

@ -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 + "]");

View File

@ -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);

View File

@ -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);
}

View File

@ -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<ScriptTransform, ScriptTransform.Result> {
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<ScriptTransfo
ScriptTransform.Result doExecute(WatchExecutionContext ctx, Payload payload) throws IOException {
Script script = transform.getScript();
WatcherScript script = transform.getScript();
Map<String, Object> model = new HashMap<>();
model.putAll(script.params());
model.putAll(createCtxModel(ctx, payload));

View File

@ -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<ScriptTransform> {
private final Script script;
private final WatcherScript script;
public Builder(Script script) {
public Builder(WatcherScript script) {
this.script = script;
}

View File

@ -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<ScriptTransform, ScriptTransform.Result, ExecutableScriptTransform> {
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;
}

View File

@ -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<SearchTransform, Se
@Inject
public SearchTransformFactory(Settings settings, InternalClient client, IndicesQueriesRegistry queryRegistry,
AggregatorParsers aggParsers, Suggesters suggesters, ScriptServiceProxy scriptService) {
AggregatorParsers aggParsers, Suggesters suggesters, ScriptService scriptService) {
this(settings, new WatcherClientProxy(settings, client), queryRegistry, aggParsers, suggesters, scriptService);
}
public SearchTransformFactory(Settings settings, WatcherClientProxy client, IndicesQueriesRegistry queryRegistry,
AggregatorParsers aggParsers, Suggesters suggesters, ScriptServiceProxy scriptService) {
AggregatorParsers aggParsers, Suggesters suggesters, ScriptService scriptService) {
super(Loggers.getLogger(ExecutableSearchTransform.class, settings));
this.client = client;
this.parseFieldMatcher = new ParseFieldMatcher(settings);

Some files were not shown because too many files have changed in this diff Show More