Skip to content

Commit 3844125

Browse files
Un-hardcode SecurityIndexManager to handle generic indices (#40064)
`SecurityIndexManager` is hardcoded to handle only the `.security`-`.security-7` alias-index pair. This commit removes the hardcoded bits, so that the `SecurityIndexManager` can be reused for other indices, such as the planned security tokens index (`.security-tokens-7`).
1 parent 97ef295 commit 3844125

File tree

4 files changed

+85
-73
lines changed

4 files changed

+85
-73
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
396396
components.add(auditTrailService);
397397
this.auditTrailService.set(auditTrailService);
398398

399-
securityIndex.set(new SecurityIndexManager(client, SecurityIndexManager.SECURITY_INDEX_NAME, clusterService));
399+
securityIndex.set(SecurityIndexManager.buildSecurityIndexManager(client, clusterService));
400400

401401
final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex.get(), clusterService);
402402
this.tokenService.set(tokenService);

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

Lines changed: 82 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import java.util.function.BiConsumer;
6363
import java.util.function.Consumer;
6464
import java.util.function.Predicate;
65+
import java.util.function.Supplier;
6566
import java.util.regex.Pattern;
6667
import java.util.stream.Collectors;
6768

@@ -72,7 +73,7 @@
7273
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
7374

7475
/**
75-
* Manages the lifecycle of a single index, its template, mapping and and data upgrades/migrations.
76+
* Manages the lifecycle of a single index, mapping and and data upgrades/migrations.
7677
*/
7778
public class SecurityIndexManager implements ClusterStateListener {
7879

@@ -82,28 +83,41 @@ public class SecurityIndexManager implements ClusterStateListener {
8283
public static final String TEMPLATE_VERSION_PATTERN = Pattern.quote("${security.template.version}");
8384
public static final String SECURITY_TEMPLATE_NAME = "security-index-template";
8485
public static final String SECURITY_INDEX_NAME = ".security";
85-
private static final Logger LOGGER = LogManager.getLogger(SecurityIndexManager.class);
86+
private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class);
8687

87-
private final String indexName;
88+
private final String aliasName;
89+
private final String internalIndexName;
90+
private final int internalIndexFormat;
91+
private final Supplier<byte[]> mappingSourceSupplier;
8892
private final Client client;
8993

9094
private final List<BiConsumer<State, State>> stateChangeListeners = new CopyOnWriteArrayList<>();
9195

9296
private volatile State indexState;
9397

94-
public SecurityIndexManager(Client client, String indexName, ClusterService clusterService) {
95-
this(client, indexName, State.UNRECOVERED_STATE);
98+
public static SecurityIndexManager buildSecurityIndexManager(Client client, ClusterService clusterService) {
99+
return new SecurityIndexManager(client, SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX, INTERNAL_INDEX_FORMAT,
100+
SecurityIndexManager::readSecurityTemplateAsBytes, clusterService);
101+
}
102+
103+
private SecurityIndexManager(Client client, String aliasName, String internalIndexName, int internalIndexFormat,
104+
Supplier<byte[]> mappingSourceSupplier, ClusterService clusterService) {
105+
this(client, aliasName, internalIndexName, internalIndexFormat, mappingSourceSupplier, State.UNRECOVERED_STATE);
96106
clusterService.addListener(this);
97107
}
98108

99-
private SecurityIndexManager(Client client, String indexName, State indexState) {
100-
this.client = client;
101-
this.indexName = indexName;
109+
private SecurityIndexManager(Client client, String aliasName, String internalIndexName, int internalIndexFormat,
110+
Supplier<byte[]> mappingSourceSupplier, State indexState) {
111+
this.aliasName = aliasName;
112+
this.internalIndexName = internalIndexName;
113+
this.internalIndexFormat = internalIndexFormat;
114+
this.mappingSourceSupplier = mappingSourceSupplier;
102115
this.indexState = indexState;
116+
this.client = client;
103117
}
104118

105119
public SecurityIndexManager freeze() {
106-
return new SecurityIndexManager(null, indexName, indexState);
120+
return new SecurityIndexManager(null, aliasName, internalIndexName, internalIndexFormat, mappingSourceSupplier, indexState);
107121
}
108122

109123
public boolean checkMappingVersion(Predicate<Version> requiredVersion) {
@@ -143,9 +157,10 @@ public ElasticsearchException getUnavailableReason() {
143157
}
144158

145159
if (localState.indexExists) {
146-
return new UnavailableShardsException(null, "at least one primary shard for the security index is unavailable");
160+
return new UnavailableShardsException(null,
161+
"at least one primary shard for the index [" + localState.concreteIndexName + "] is unavailable");
147162
} else {
148-
return new IndexNotFoundException(SECURITY_INDEX_NAME);
163+
return new IndexNotFoundException(localState.concreteIndexName);
149164
}
150165
}
151166

@@ -163,20 +178,20 @@ public void clusterChanged(ClusterChangedEvent event) {
163178
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
164179
// wait until the gateway has recovered from disk, otherwise we think we don't have the
165180
// .security index but they may not have been restored from the cluster state on disk
166-
LOGGER.debug("security index manager waiting until state has been recovered");
181+
logger.debug("security index manager waiting until state has been recovered");
167182
return;
168183
}
169184
final State previousState = indexState;
170-
final IndexMetaData indexMetaData = resolveConcreteIndex(indexName, event.state().metaData());
185+
final IndexMetaData indexMetaData = resolveConcreteIndex(aliasName, event.state().metaData());
171186
final boolean indexExists = indexMetaData != null;
172187
final boolean isIndexUpToDate = indexExists == false ||
173-
INDEX_FORMAT_SETTING.get(indexMetaData.getSettings()).intValue() == INTERNAL_INDEX_FORMAT;
188+
INDEX_FORMAT_SETTING.get(indexMetaData.getSettings()).intValue() == internalIndexFormat;
174189
final boolean indexAvailable = checkIndexAvailable(event.state());
175190
final boolean mappingIsUpToDate = indexExists == false || checkIndexMappingUpToDate(event.state());
176191
final Version mappingVersion = oldestIndexMappingVersion(event.state());
177192
final ClusterHealthStatus indexStatus = indexMetaData == null ? null :
178193
new ClusterIndexHealth(indexMetaData, event.state().getRoutingTable().index(indexMetaData.getIndex())).getStatus();
179-
final String concreteIndexName = indexMetaData == null ? INTERNAL_SECURITY_INDEX : indexMetaData.getIndex().getName();
194+
final String concreteIndexName = indexMetaData == null ? internalIndexName : indexMetaData.getIndex().getName();
180195
final State newState = new State(indexExists, isIndexUpToDate, indexAvailable, mappingIsUpToDate, mappingVersion, concreteIndexName,
181196
indexStatus);
182197
this.indexState = newState;
@@ -193,61 +208,55 @@ private boolean checkIndexAvailable(ClusterState state) {
193208
if (routingTable != null && routingTable.allPrimaryShardsActive()) {
194209
return true;
195210
}
196-
LOGGER.debug("Security index [{}] is not yet active", indexName);
211+
logger.debug("Index [{}] is not yet active", aliasName);
197212
return false;
198213
}
199214

200215
/**
201216
* Returns the routing-table for this index, or <code>null</code> if the index does not exist.
202217
*/
203218
private IndexRoutingTable getIndexRoutingTable(ClusterState clusterState) {
204-
IndexMetaData metaData = resolveConcreteIndex(indexName, clusterState.metaData());
219+
IndexMetaData metaData = resolveConcreteIndex(aliasName, clusterState.metaData());
205220
if (metaData == null) {
206221
return null;
207222
} else {
208223
return clusterState.routingTable().index(metaData.getIndex());
209224
}
210225
}
211226

212-
public static boolean checkTemplateExistsAndVersionMatches(
213-
String templateName, ClusterState state, Logger logger, Predicate<Version> predicate) {
214-
215-
return TemplateUtils.checkTemplateExistsAndVersionMatches(templateName, SECURITY_VERSION_STRING,
216-
state, logger, predicate);
227+
public static boolean checkTemplateExistsAndVersionMatches(String templateName, ClusterState state, Logger logger,
228+
Predicate<Version> predicate) {
229+
return TemplateUtils.checkTemplateExistsAndVersionMatches(templateName, SECURITY_VERSION_STRING, state, logger, predicate);
217230
}
218231

219232
private boolean checkIndexMappingUpToDate(ClusterState clusterState) {
220233
return checkIndexMappingVersionMatches(clusterState, Version.CURRENT::equals);
221234
}
222235

223-
private boolean checkIndexMappingVersionMatches(ClusterState clusterState,
224-
Predicate<Version> predicate) {
225-
return checkIndexMappingVersionMatches(indexName, clusterState, LOGGER, predicate);
236+
private boolean checkIndexMappingVersionMatches(ClusterState clusterState, Predicate<Version> predicate) {
237+
return checkIndexMappingVersionMatches(aliasName, clusterState, logger, predicate);
226238
}
227239

228-
public static boolean checkIndexMappingVersionMatches(String indexName,
229-
ClusterState clusterState, Logger logger,
240+
public static boolean checkIndexMappingVersionMatches(String indexName, ClusterState clusterState, Logger logger,
230241
Predicate<Version> predicate) {
231-
return loadIndexMappingVersions(indexName, clusterState, logger)
232-
.stream().allMatch(predicate);
242+
return loadIndexMappingVersions(indexName, clusterState, logger).stream().allMatch(predicate);
233243
}
234244

235245
private Version oldestIndexMappingVersion(ClusterState clusterState) {
236-
final Set<Version> versions = loadIndexMappingVersions(indexName, clusterState, LOGGER);
246+
final Set<Version> versions = loadIndexMappingVersions(aliasName, clusterState, logger);
237247
return versions.stream().min(Version::compareTo).orElse(null);
238248
}
239249

240-
private static Set<Version> loadIndexMappingVersions(String indexName,
241-
ClusterState clusterState, Logger logger) {
250+
private static Set<Version> loadIndexMappingVersions(String aliasName, ClusterState clusterState, Logger logger) {
242251
Set<Version> versions = new HashSet<>();
243-
IndexMetaData indexMetaData = resolveConcreteIndex(indexName, clusterState.metaData());
252+
IndexMetaData indexMetaData = resolveConcreteIndex(aliasName, clusterState.metaData());
244253
if (indexMetaData != null) {
245254
for (Object object : indexMetaData.getMappings().values().toArray()) {
246255
MappingMetaData mappingMetaData = (MappingMetaData) object;
247256
if (mappingMetaData.type().equals(MapperService.DEFAULT_MAPPING)) {
248257
continue;
249258
}
250-
versions.add(readMappingVersion(indexName, mappingMetaData, logger));
259+
versions.add(readMappingVersion(aliasName, mappingMetaData, logger));
251260
}
252261
}
253262
return versions;
@@ -270,8 +279,7 @@ private static IndexMetaData resolveConcreteIndex(final String indexOrAliasName,
270279
return null;
271280
}
272281

273-
private static Version readMappingVersion(String indexName, MappingMetaData mappingMetaData,
274-
Logger logger) {
282+
private static Version readMappingVersion(String indexName, MappingMetaData mappingMetaData, Logger logger) {
275283
try {
276284
Map<String, Object> meta =
277285
(Map<String, Object>) mappingMetaData.sourceAsMap().get("_meta");
@@ -289,17 +297,17 @@ private static Version readMappingVersion(String indexName, MappingMetaData mapp
289297
}
290298

291299
/**
292-
* Validates the security index is up to date and does not need to migrated. If it is not, the
293-
* consumer is called with an exception. If the security index is up to date, the runnable will
300+
* Validates that the index is up to date and does not need to be migrated. If it is not, the
301+
* consumer is called with an exception. If the index is up to date, the runnable will
294302
* be executed. <b>NOTE:</b> this method does not check the availability of the index; this check
295303
* is left to the caller so that this condition can be handled appropriately.
296304
*/
297305
public void checkIndexVersionThenExecute(final Consumer<Exception> consumer, final Runnable andThen) {
298306
final State indexState = this.indexState; // use a local copy so all checks execute against the same state!
299307
if (indexState.indexExists && indexState.isIndexUpToDate == false) {
300308
consumer.accept(new IllegalStateException(
301-
"Security index is not on the current version. Security features relying on the index will not be available until " +
302-
"the upgrade API is run on the security index"));
309+
"Index [" + indexState.concreteIndexName + "] is not on the current version. Security features relying on the index"
310+
+ " will not be available until the upgrade API is run on the index"));
303311
} else {
304312
andThen.run();
305313
}
@@ -313,17 +321,20 @@ public void prepareIndexIfNeededThenExecute(final Consumer<Exception> consumer,
313321
final State indexState = this.indexState; // use a local copy so all checks execute against the same state!
314322
// TODO we should improve this so we don't fire off a bunch of requests to do the same thing (create or update mappings)
315323
if (indexState == State.UNRECOVERED_STATE) {
316-
consumer.accept(new ElasticsearchStatusException("Cluster state has not been recovered yet, cannot write to the security index",
324+
consumer.accept(new ElasticsearchStatusException(
325+
"Cluster state has not been recovered yet, cannot write to the [" + indexState.concreteIndexName + "] index",
317326
RestStatus.SERVICE_UNAVAILABLE));
318327
} else if (indexState.indexExists && indexState.isIndexUpToDate == false) {
319328
consumer.accept(new IllegalStateException(
320-
"Security index is not on the current version. Security features relying on the index will not be available until " +
321-
"the upgrade API is run on the security index"));
329+
"Index [" + indexState.concreteIndexName + "] is not on the current version."
330+
+ "Security features relying on the index will not be available until the upgrade API is run on the index"));
322331
} else if (indexState.indexExists == false) {
323-
LOGGER.info("security index does not exist. Creating [{}] with alias [{}]", INTERNAL_SECURITY_INDEX, SECURITY_INDEX_NAME);
324-
Tuple<String, Settings> mappingAndSettings = loadMappingAndSettingsSourceFromTemplate();
325-
CreateIndexRequest request = new CreateIndexRequest(INTERNAL_SECURITY_INDEX)
326-
.alias(new Alias(SECURITY_INDEX_NAME))
332+
assert indexState.concreteIndexName != null;
333+
logger.info("security index does not exist. Creating [{}] with alias [{}]", indexState.concreteIndexName, this.aliasName);
334+
final byte[] mappingSource = mappingSourceSupplier.get();
335+
final Tuple<String, Settings> mappingAndSettings = parseMappingAndSettingsFromTemplateBytes(mappingSource);
336+
CreateIndexRequest request = new CreateIndexRequest(indexState.concreteIndexName)
337+
.alias(new Alias(this.aliasName))
327338
.mapping(MapperService.SINGLE_MAPPING_NAME, mappingAndSettings.v1(), XContentType.JSON)
328339
.waitForActiveShards(ActiveShardCount.ALL)
329340
.settings(mappingAndSettings.v2());
@@ -351,11 +362,11 @@ public void onFailure(Exception e) {
351362
}
352363
}, client.admin().indices()::create);
353364
} else if (indexState.mappingUpToDate == false) {
354-
LOGGER.info(
355-
"security index [{}] (alias [{}]) is not up to date. Updating mapping", indexState.concreteIndexName, SECURITY_INDEX_NAME);
356-
365+
logger.info("Index [{}] (alias [{}]) is not up to date. Updating mapping", indexState.concreteIndexName, this.aliasName);
366+
final byte[] mappingSource = mappingSourceSupplier.get();
367+
final Tuple<String, Settings> mappingAndSettings = parseMappingAndSettingsFromTemplateBytes(mappingSource);
357368
PutMappingRequest request = new PutMappingRequest(indexState.concreteIndexName)
358-
.source(loadMappingAndSettingsSourceFromTemplate().v1(), XContentType.JSON)
369+
.source(mappingAndSettings.v1(), XContentType.JSON)
359370
.type(MapperService.SINGLE_MAPPING_NAME);
360371
executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request,
361372
ActionListener.<AcknowledgedResponse>wrap(putMappingResponse -> {
@@ -370,11 +381,28 @@ public void onFailure(Exception e) {
370381
}
371382
}
372383

373-
private Tuple<String, Settings> loadMappingAndSettingsSourceFromTemplate() {
374-
final byte[] template = TemplateUtils.loadTemplate("/" + SECURITY_TEMPLATE_NAME + ".json", Version.CURRENT.toString(),
384+
/**
385+
* Return true if the state moves from an unhealthy ("RED") index state to a healthy ("non-RED") state.
386+
*/
387+
public static boolean isMoveFromRedToNonRed(State previousState, State currentState) {
388+
return (previousState.indexStatus == null || previousState.indexStatus == ClusterHealthStatus.RED)
389+
&& currentState.indexStatus != null && currentState.indexStatus != ClusterHealthStatus.RED;
390+
}
391+
392+
/**
393+
* Return true if the state moves from the index existing to the index not existing.
394+
*/
395+
public static boolean isIndexDeleted(State previousState, State currentState) {
396+
return previousState.indexStatus != null && currentState.indexStatus == null;
397+
}
398+
399+
private static byte[] readSecurityTemplateAsBytes() {
400+
return TemplateUtils.loadTemplate("/" + SECURITY_TEMPLATE_NAME + ".json", Version.CURRENT.toString(),
375401
SecurityIndexManager.TEMPLATE_VERSION_PATTERN).getBytes(StandardCharsets.UTF_8);
376-
final PutIndexTemplateRequest request = new PutIndexTemplateRequest(SECURITY_TEMPLATE_NAME).source(template, XContentType.JSON);
402+
}
377403

404+
private static Tuple<String, Settings> parseMappingAndSettingsFromTemplateBytes(byte[] template) {
405+
final PutIndexTemplateRequest request = new PutIndexTemplateRequest("name_is_not_important").source(template, XContentType.JSON);
378406
final String mappingSource = request.mappings().get(MapperService.SINGLE_MAPPING_NAME);
379407
try (XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY,
380408
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, mappingSource)) {
@@ -391,21 +419,6 @@ private Tuple<String, Settings> loadMappingAndSettingsSourceFromTemplate() {
391419
}
392420
}
393421

394-
/**
395-
* Return true if the state moves from an unhealthy ("RED") index state to a healthy ("non-RED") state.
396-
*/
397-
public static boolean isMoveFromRedToNonRed(State previousState, State currentState) {
398-
return (previousState.indexStatus == null || previousState.indexStatus == ClusterHealthStatus.RED)
399-
&& currentState.indexStatus != null && currentState.indexStatus != ClusterHealthStatus.RED;
400-
}
401-
402-
/**
403-
* Return true if the state moves from the index existing to the index not existing.
404-
*/
405-
public static boolean isIndexDeleted(State previousState, State currentState) {
406-
return previousState.indexStatus != null && currentState.indexStatus == null;
407-
}
408-
409422
/**
410423
* State of the security index.
411424
*/

0 commit comments

Comments
 (0)