Skip to content

Do not allow modify aliases on followers #43017

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksAction;
import org.elasticsearch.action.admin.cluster.tasks.TransportPendingClusterTasksAction;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.TransportIndicesAliasesAction;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistAction;
import org.elasticsearch.action.admin.indices.alias.exists.TransportAliasesExistAction;
Expand Down Expand Up @@ -361,6 +362,7 @@ public class ActionModule extends AbstractModule {
private final DestructiveOperations destructiveOperations;
private final RestController restController;
private final RequestValidators<PutMappingRequest> mappingRequestValidators;
private final RequestValidators<IndicesAliasesRequest> indicesAliasesRequestRequestValidators;

public ActionModule(Settings settings, IndexNameExpressionResolver indexNameExpressionResolver,
IndexScopedSettings indexScopedSettings, ClusterSettings clusterSettings, SettingsFilter settingsFilter,
Expand Down Expand Up @@ -393,6 +395,8 @@ public ActionModule(Settings settings, IndexNameExpressionResolver indexNameExpr
}
mappingRequestValidators = new RequestValidators<>(
actionPlugins.stream().flatMap(p -> p.mappingRequestValidators().stream()).collect(Collectors.toList()));
indicesAliasesRequestRequestValidators = new RequestValidators<>(
actionPlugins.stream().flatMap(p -> p.indicesAliasesRequestValidators().stream()).collect(Collectors.toList()));

restController = new RestController(headers, restWrapper, nodeClient, circuitBreakerService, usageService);
}
Expand Down Expand Up @@ -686,6 +690,7 @@ protected void configure() {
bind(ActionFilters.class).toInstance(actionFilters);
bind(DestructiveOperations.class).toInstance(destructiveOperations);
bind(new TypeLiteral<RequestValidators<PutMappingRequest>>() {}).toInstance(mappingRequestValidators);
bind(new TypeLiteral<RequestValidators<IndicesAliasesRequest>>() {}).toInstance(indicesAliasesRequestRequestValidators);
bind(AutoCreateIndex.class).toInstance(autoCreateIndex);
bind(TransportLivenessAction.class).asEagerSingleton();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.elasticsearch.action.admin.indices.alias;

import org.elasticsearch.ElasticsearchGenerationException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.AliasesRequest;
import org.elasticsearch.action.support.IndicesOptions;
Expand Down Expand Up @@ -62,6 +63,7 @@
public class IndicesAliasesRequest extends AcknowledgedRequest<IndicesAliasesRequest> implements ToXContentObject {

private List<AliasActions> allAliasActions = new ArrayList<>();
private String origin = "";

// indices options that require every specified index to exist, expand wildcards only to open
// indices, don't allow that no indices are resolved from wildcard expressions and resolve the
Expand Down Expand Up @@ -526,6 +528,15 @@ public int hashCode() {
}
}

public String origin() {
return origin;
}

public IndicesAliasesRequest origin(final String origin) {
this.origin = Objects.requireNonNull(origin);
return this;
}

/**
* Add the action to this request and validate it.
*/
Expand Down Expand Up @@ -556,12 +567,23 @@ public ActionRequestValidationException validate() {
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
allAliasActions = in.readList(AliasActions::new);
if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
origin = in.readOptionalString();
} else {
origin = null;
}
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeList(allAliasActions);
// noinspection StatementWithEmptyBody
if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
out.writeOptionalString(origin);
} else {
// nothing to do here, here for symmetry with IndicesAliasesRequest#readFrom
}
}

public IndicesOptions indicesOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.carrotsearch.hppc.cursors.ObjectCursor;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
Expand All @@ -37,6 +38,7 @@
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.index.Index;
import org.elasticsearch.rest.action.admin.indices.AliasesNotFoundException;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
Expand All @@ -45,6 +47,8 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import static java.util.Collections.unmodifiableList;
Expand All @@ -55,14 +59,21 @@
public class TransportIndicesAliasesAction extends TransportMasterNodeAction<IndicesAliasesRequest, AcknowledgedResponse> {

private final MetaDataIndexAliasesService indexAliasesService;
private final RequestValidators<IndicesAliasesRequest> requestValidators;

@Inject
public TransportIndicesAliasesAction(TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, MetaDataIndexAliasesService indexAliasesService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
public TransportIndicesAliasesAction(
final TransportService transportService,
final ClusterService clusterService,
final ThreadPool threadPool,
final MetaDataIndexAliasesService indexAliasesService,
final ActionFilters actionFilters,
final IndexNameExpressionResolver indexNameExpressionResolver,
final RequestValidators<IndicesAliasesRequest> requestValidators) {
super(IndicesAliasesAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver,
IndicesAliasesRequest::new);
this.indexAliasesService = indexAliasesService;
this.requestValidators = Objects.requireNonNull(requestValidators);
}

@Override
Expand Down Expand Up @@ -96,23 +107,28 @@ protected void masterOperation(final IndicesAliasesRequest request, final Cluste
// Resolve all the AliasActions into AliasAction instances and gather all the aliases
Set<String> aliases = new HashSet<>();
for (AliasActions action : actions) {
String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(state, request.indicesOptions(), action.indices());
final Index[] concreteIndices = indexNameExpressionResolver.concreteIndices(state, request.indicesOptions(), action.indices());
final Optional<Exception> maybeException = requestValidators.validateRequest(request, state, concreteIndices);
if (maybeException.isPresent()) {
listener.onFailure(maybeException.get());
return;
}
Collections.addAll(aliases, action.getOriginalAliases());
for (String index : concreteIndices) {
for (final Index index : concreteIndices) {
switch (action.actionType()) {
case ADD:
for (String alias : concreteAliases(action, state.metaData(), index)) {
finalActions.add(new AliasAction.Add(index, alias, action.filter(), action.indexRouting(),
for (String alias : concreteAliases(action, state.metaData(), index.getName())) {
finalActions.add(new AliasAction.Add(index.getName(), alias, action.filter(), action.indexRouting(),
action.searchRouting(), action.writeIndex()));
}
break;
case REMOVE:
for (String alias : concreteAliases(action, state.metaData(), index)) {
finalActions.add(new AliasAction.Remove(index, alias));
for (String alias : concreteAliases(action, state.metaData(), index.getName())) {
finalActions.add(new AliasAction.Remove(index.getName(), alias));
}
break;
case REMOVE_INDEX:
finalActions.add(new AliasAction.RemoveIndex(index));
finalActions.add(new AliasAction.RemoveIndex(index.getName()));
break;
default:
throw new IllegalArgumentException("Unsupported action [" + action.actionType() + "]");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package org.elasticsearch.action.admin.indices.mapping.put;

import com.carrotsearch.hppc.ObjectHashSet;

import org.elasticsearch.ElasticsearchGenerationException;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.IndicesRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.action.support.TransportAction;
Expand Down Expand Up @@ -190,4 +191,8 @@ default Collection<RequestValidators.RequestValidator<PutMappingRequest>> mappin
return Collections.emptyList();
}

default Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return Collections.emptyList();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.action.admin.indices.alias;

import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistResponse;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;

public class ValidateIndiesAliasesRequestIT extends ESSingleNodeTestCase {

public static class IndicesAliasesPlugin extends Plugin implements ActionPlugin {

static final Setting<List<String>> ALLOWED_ORIGINS_SETTING = Setting.listSetting(
"index.aliases.allowed_origins",
Collections.emptyList(),
Function.identity(),
Setting.Property.IndexScope,
Setting.Property.Dynamic);

@Override
public List<Setting<?>> getSettings() {
return Collections.singletonList(ALLOWED_ORIGINS_SETTING);
}

@Override
public Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return Collections.singletonList((request, state, indices) -> {
for (final Index index : indices) {
final List<String> allowedOrigins = ALLOWED_ORIGINS_SETTING.get(state.metaData().index(index).getSettings());
if (allowedOrigins.contains(request.origin()) == false) {
final String message = String.format(
Locale.ROOT,
"origin [%s] not allowed for index [%s]",
request.origin(),
index.getName());
return Optional.of(new IllegalStateException(message));
}
}
return Optional.empty();
});
}

}

@Override
protected Collection<Class<? extends Plugin>> getPlugins() {
return Collections.singletonList(IndicesAliasesPlugin.class);
}

public void testAllowed() {
final Settings settings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("allowed"))
.build();
createIndex("index", settings);
final IndicesAliasesRequest request = new IndicesAliasesRequest().origin("allowed");
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("index").alias("alias"));
assertAcked(client().admin().indices().aliases(request).actionGet());
final GetAliasesResponse response = client().admin().indices().getAliases(new GetAliasesRequest("alias")).actionGet();
assertThat(response.getAliases().keys().size(), equalTo(1));
assertThat(response.getAliases().keys().iterator().next().value, equalTo("index"));
final List<AliasMetaData> aliasMetaData = response.getAliases().get("index");
assertThat(aliasMetaData, hasSize(1));
assertThat(aliasMetaData.get(0).alias(), equalTo("alias"));
}

public void testNotAllowed() {
final Settings settings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("allowed"))
.build();
createIndex("index", settings);
final String origin = randomFrom("", "not-allowed");
final IndicesAliasesRequest request = new IndicesAliasesRequest().origin(origin);
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("index").alias("alias"));
final Exception e = expectThrows(IllegalStateException.class, () -> client().admin().indices().aliases(request).actionGet());
assertThat(e, hasToString(containsString("origin [" + origin + "] not allowed for index [index]")));
}

public void testSomeAllowed() {
final Settings fooIndexSettings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("foo_allowed"))
.build();
createIndex("foo", fooIndexSettings);
final Settings barIndexSettings = Settings.builder()
.putList(IndicesAliasesPlugin.ALLOWED_ORIGINS_SETTING.getKey(), Collections.singletonList("bar_allowed"))
.build();
createIndex("bar", barIndexSettings);
final String origin = randomFrom("foo_allowed", "bar_allowed");
final IndicesAliasesRequest request = new IndicesAliasesRequest().origin(origin);
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("foo").alias("alias"));
request.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("bar").alias("alias"));
final Exception e = expectThrows(IllegalStateException.class, () -> client().admin().indices().aliases(request).actionGet());
final String index = "foo_allowed".equals(origin) ? "bar" : "foo";
assertThat(e, hasToString(containsString("origin [" + origin + "] not allowed for index [" + index + "]")));
final AliasesExistResponse response = client().admin().indices().aliasesExist(new GetAliasesRequest("alias")).actionGet();
assertFalse(response.exists());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
Expand Down Expand Up @@ -349,4 +350,8 @@ public Collection<RequestValidators.RequestValidator<PutMappingRequest>> mapping
return Collections.singletonList(CcrRequests.CCR_PUT_MAPPING_REQUEST_VALIDATOR);
}

@Override
public Collection<RequestValidators.RequestValidator<IndicesAliasesRequest>> indicesAliasesRequestValidators() {
return Collections.singletonList(CcrRequests.CCR_INDICES_ALIASES_REQUEST_VALIDATOR);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.RequestValidators;
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexMetaData;
Expand Down Expand Up @@ -105,4 +106,23 @@ public static void getIndexMetadata(Client client, Index index, long mappingVers
return Optional.empty();
};

public static final RequestValidators.RequestValidator<IndicesAliasesRequest> CCR_INDICES_ALIASES_REQUEST_VALIDATOR =
(request, state, indices) -> {
if (request.origin() == null) {
return Optional.empty(); // an indices aliases request on old versions does not have origin
}
final List<Index> followingIndices = Arrays.stream(indices)
.filter(index -> {
final IndexMetaData indexMetaData = state.metaData().index(index);
return indexMetaData != null && CcrSettings.CCR_FOLLOWING_INDEX_SETTING.get(indexMetaData.getSettings());
}).collect(Collectors.toList());
if (followingIndices.isEmpty() == false && "ccr".equals(request.origin()) == false) {
final String errorMessage = "can't modify aliases on indices "
+ "[" + followingIndices.stream().map(Index::getName).collect(Collectors.joining(", ")) + "]; "
+ "aliases of following indices are self-replicated from their leader indices";
return Optional.of(new ElasticsearchStatusException(errorMessage, RestStatus.FORBIDDEN));
}
return Optional.empty();
};

}
Loading