Skip to content

Commit af937b1

Browse files
SecurityIndexManager handle RuntimeEx while reading mapping (#44409)
Fixes exception handling while reading and parsing `.security-*` mappings and templates.
1 parent 383d7b7 commit af937b1

File tree

2 files changed

+82
-59
lines changed

2 files changed

+82
-59
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java

+61-59
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ private SecurityIndexManager(Client client, ClusterService clusterService, Strin
117117
clusterService.addListener(this);
118118
}
119119

120-
private SecurityIndexManager(Client client, String aliasName, String internalIndexName, int internalIndexFormat,
120+
// protected for testing
121+
protected SecurityIndexManager(Client client, String aliasName, String internalIndexName, int internalIndexFormat,
121122
Supplier<byte[]> mappingSourceSupplier, State indexState) {
122123
this.aliasName = aliasName;
123124
this.internalIndexName = internalIndexName;
@@ -351,65 +352,68 @@ public void checkIndexVersionThenExecute(final Consumer<Exception> consumer, fin
351352
*/
352353
public void prepareIndexIfNeededThenExecute(final Consumer<Exception> consumer, final Runnable andThen) {
353354
final State indexState = this.indexState; // use a local copy so all checks execute against the same state!
354-
// TODO we should improve this so we don't fire off a bunch of requests to do the same thing (create or update mappings)
355-
if (indexState == State.UNRECOVERED_STATE) {
356-
consumer.accept(new ElasticsearchStatusException(
357-
"Cluster state has not been recovered yet, cannot write to the [" + indexState.concreteIndexName + "] index",
358-
RestStatus.SERVICE_UNAVAILABLE));
359-
} else if (indexState.indexExists() && indexState.isIndexUpToDate == false) {
360-
consumer.accept(new IllegalStateException(
361-
"Index [" + indexState.concreteIndexName + "] is not on the current version."
362-
+ "Security features relying on the index will not be available until the upgrade API is run on the index"));
363-
} else if (indexState.indexExists() == false) {
364-
assert indexState.concreteIndexName != null;
365-
logger.info("security index does not exist. Creating [{}] with alias [{}]", indexState.concreteIndexName, this.aliasName);
366-
final byte[] mappingSource = mappingSourceSupplier.get();
367-
final Tuple<String, Settings> mappingAndSettings = parseMappingAndSettingsFromTemplateBytes(mappingSource);
368-
CreateIndexRequest request = new CreateIndexRequest(indexState.concreteIndexName)
369-
.alias(new Alias(this.aliasName))
370-
.mapping(MapperService.SINGLE_MAPPING_NAME, mappingAndSettings.v1(), XContentType.JSON)
371-
.waitForActiveShards(ActiveShardCount.ALL)
372-
.settings(mappingAndSettings.v2());
373-
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
374-
new ActionListener<CreateIndexResponse>() {
375-
@Override
376-
public void onResponse(CreateIndexResponse createIndexResponse) {
377-
if (createIndexResponse.isAcknowledged()) {
378-
andThen.run();
379-
} else {
380-
consumer.accept(new ElasticsearchException("Failed to create security index"));
381-
}
382-
}
383-
384-
@Override
385-
public void onFailure(Exception e) {
386-
final Throwable cause = ExceptionsHelper.unwrapCause(e);
387-
if (cause instanceof ResourceAlreadyExistsException) {
388-
// the index already exists - it was probably just created so this
389-
// node hasn't yet received the cluster state update with the index
390-
andThen.run();
391-
} else {
392-
consumer.accept(e);
393-
}
355+
try {
356+
// TODO we should improve this so we don't fire off a bunch of requests to do the same thing (create or update mappings)
357+
if (indexState == State.UNRECOVERED_STATE) {
358+
throw new ElasticsearchStatusException(
359+
"Cluster state has not been recovered yet, cannot write to the [" + indexState.concreteIndexName + "] index",
360+
RestStatus.SERVICE_UNAVAILABLE);
361+
} else if (indexState.indexExists() && indexState.isIndexUpToDate == false) {
362+
throw new IllegalStateException("Index [" + indexState.concreteIndexName + "] is not on the current version."
363+
+ "Security features relying on the index will not be available until the upgrade API is run on the index");
364+
} else if (indexState.indexExists() == false) {
365+
assert indexState.concreteIndexName != null;
366+
logger.info("security index does not exist. Creating [{}] with alias [{}]", indexState.concreteIndexName, this.aliasName);
367+
final byte[] mappingSource = mappingSourceSupplier.get();
368+
final Tuple<String, Settings> mappingAndSettings = parseMappingAndSettingsFromTemplateBytes(mappingSource);
369+
CreateIndexRequest request = new CreateIndexRequest(indexState.concreteIndexName)
370+
.alias(new Alias(this.aliasName))
371+
.mapping(MapperService.SINGLE_MAPPING_NAME, mappingAndSettings.v1(), XContentType.JSON)
372+
.waitForActiveShards(ActiveShardCount.ALL)
373+
.settings(mappingAndSettings.v2());
374+
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
375+
new ActionListener<CreateIndexResponse>() {
376+
@Override
377+
public void onResponse(CreateIndexResponse createIndexResponse) {
378+
if (createIndexResponse.isAcknowledged()) {
379+
andThen.run();
380+
} else {
381+
consumer.accept(new ElasticsearchException("Failed to create security index"));
394382
}
395-
}, client.admin().indices()::create);
396-
} else if (indexState.mappingUpToDate == false) {
397-
logger.info("Index [{}] (alias [{}]) is not up to date. Updating mapping", indexState.concreteIndexName, this.aliasName);
398-
final byte[] mappingSource = mappingSourceSupplier.get();
399-
final Tuple<String, Settings> mappingAndSettings = parseMappingAndSettingsFromTemplateBytes(mappingSource);
400-
PutMappingRequest request = new PutMappingRequest(indexState.concreteIndexName)
401-
.source(mappingAndSettings.v1(), XContentType.JSON)
402-
.type(MapperService.SINGLE_MAPPING_NAME);
403-
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
404-
ActionListener.<AcknowledgedResponse>wrap(putMappingResponse -> {
405-
if (putMappingResponse.isAcknowledged()) {
383+
}
384+
385+
@Override
386+
public void onFailure(Exception e) {
387+
final Throwable cause = ExceptionsHelper.unwrapCause(e);
388+
if (cause instanceof ResourceAlreadyExistsException) {
389+
// the index already exists - it was probably just created so this
390+
// node hasn't yet received the cluster state update with the index
406391
andThen.run();
407392
} else {
408-
consumer.accept(new IllegalStateException("put mapping request was not acknowledged"));
393+
consumer.accept(e);
409394
}
410-
}, consumer), client.admin().indices()::putMapping);
411-
} else {
412-
andThen.run();
395+
}
396+
}, client.admin().indices()::create);
397+
} else if (indexState.mappingUpToDate == false) {
398+
logger.info("Index [{}] (alias [{}]) is not up to date. Updating mapping", indexState.concreteIndexName, this.aliasName);
399+
final byte[] mappingSource = mappingSourceSupplier.get();
400+
final Tuple<String, Settings> mappingAndSettings = parseMappingAndSettingsFromTemplateBytes(mappingSource);
401+
PutMappingRequest request = new PutMappingRequest(indexState.concreteIndexName)
402+
.source(mappingAndSettings.v1(), XContentType.JSON)
403+
.type(MapperService.SINGLE_MAPPING_NAME);
404+
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
405+
ActionListener.<AcknowledgedResponse>wrap(putMappingResponse -> {
406+
if (putMappingResponse.isAcknowledged()) {
407+
andThen.run();
408+
} else {
409+
consumer.accept(new IllegalStateException("put mapping request was not acknowledged"));
410+
}
411+
}, consumer), client.admin().indices()::putMapping);
412+
} else {
413+
andThen.run();
414+
}
415+
} catch (Exception e) {
416+
consumer.accept(e);
413417
}
414418
}
415419

@@ -433,7 +437,7 @@ private static byte[] readTemplateAsBytes(String templateName) {
433437
SecurityIndexManager.TEMPLATE_VERSION_PATTERN).getBytes(StandardCharsets.UTF_8);
434438
}
435439

436-
private static Tuple<String, Settings> parseMappingAndSettingsFromTemplateBytes(byte[] template) {
440+
private static Tuple<String, Settings> parseMappingAndSettingsFromTemplateBytes(byte[] template) throws IOException {
437441
final PutIndexTemplateRequest request = new PutIndexTemplateRequest("name_is_not_important").source(template, XContentType.JSON);
438442
final String mappingSource = request.mappings().get(MapperService.SINGLE_MAPPING_NAME);
439443
try (XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY,
@@ -446,8 +450,6 @@ private static Tuple<String, Settings> parseMappingAndSettingsFromTemplateBytes(
446450
XContentBuilder builder = JsonXContent.contentBuilder();
447451
builder.generator().copyCurrentStructure(parser);
448452
return new Tuple<>(Strings.toString(builder), request.settings());
449-
} catch (IOException e) {
450-
throw ExceptionsHelper.convertToRuntime(e);
451453
}
452454
}
453455

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerTests.java

+21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.elasticsearch.xpack.security.support;
77

88
import java.io.IOException;
9+
import java.time.Instant;
910
import java.util.Arrays;
1011
import java.util.HashMap;
1112
import java.util.LinkedHashMap;
@@ -14,6 +15,7 @@
1415
import java.util.concurrent.atomic.AtomicBoolean;
1516
import java.util.concurrent.atomic.AtomicReference;
1617
import java.util.function.BiConsumer;
18+
import java.util.function.Supplier;
1719

1820
import org.elasticsearch.ElasticsearchStatusException;
1921
import org.elasticsearch.Version;
@@ -67,6 +69,8 @@
6769
import static org.hamcrest.Matchers.nullValue;
6870
import static org.mockito.Mockito.mock;
6971
import static org.mockito.Mockito.when;
72+
import static org.mockito.Mockito.never;
73+
import static org.mockito.Mockito.verify;
7074

7175
public class SecurityIndexManagerTests extends ESTestCase {
7276

@@ -97,6 +101,23 @@ void doExecute(ActionType<Response> action, Request request, ActionListener<Resp
97101
}
98102
};
99103
manager = SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService);
104+
105+
}
106+
107+
public void testIndexWithFaultyMappingOnDisk() {
108+
SecurityIndexManager.State state = new SecurityIndexManager.State(randomBoolean() ? Instant.now() : null, true, randomBoolean(),
109+
false, null, "not_important", null, null);
110+
Supplier<byte[]> mappingSourceSupplier = () -> {
111+
throw new RuntimeException();
112+
};
113+
Runnable runnable = mock(Runnable.class);
114+
manager = new SecurityIndexManager(mock(Client.class), RestrictedIndicesNames.SECURITY_MAIN_ALIAS,
115+
RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT,
116+
mappingSourceSupplier, state);
117+
AtomicReference<Exception> exceptionConsumer = new AtomicReference<>();
118+
manager.prepareIndexIfNeededThenExecute(e -> exceptionConsumer.set(e), runnable);
119+
verify(runnable, never()).run();
120+
assertThat(exceptionConsumer.get(), is(notNullValue()));
100121
}
101122

102123
public void testIndexWithUpToDateMappingAndTemplate() throws IOException {

0 commit comments

Comments
 (0)