Skip to content

Commit

Permalink
Cannot query role metadata until automatic migration completed
Browse files Browse the repository at this point in the history
  • Loading branch information
albertzaharovits committed Jun 17, 2024
1 parent 68152a5 commit c5e6012
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,73 @@

package org.elasticsearch.xpack.security.action.role;

import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.role.QueryRoleAction;
import org.elasticsearch.xpack.core.security.action.role.QueryRoleRequest;
import org.elasticsearch.xpack.core.security.action.role.QueryRoleResponse;
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.elasticsearch.xpack.security.support.FieldNameTranslators;
import org.elasticsearch.xpack.security.support.RoleBoolQueryBuilder;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;

import java.util.concurrent.atomic.AtomicBoolean;

import static org.elasticsearch.xpack.security.support.FieldNameTranslators.ROLE_FIELD_NAME_TRANSLATORS;
import static org.elasticsearch.xpack.security.support.SecurityMigrations.ROLE_METADATA_FLATTENED_MIGRATION_VERSION;
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS;

public class TransportQueryRoleAction extends TransportAction<QueryRoleRequest, QueryRoleResponse> {

private final NativeRolesStore nativeRolesStore;
private final SecurityIndexManager securityIndex;

@Inject
public TransportQueryRoleAction(ActionFilters actionFilters, NativeRolesStore nativeRolesStore, TransportService transportService) {
public TransportQueryRoleAction(
ActionFilters actionFilters,
NativeRolesStore nativeRolesStore,
SecurityIndexManager securityIndex,
TransportService transportService
) {
super(QueryRoleAction.NAME, actionFilters, transportService.getTaskManager());
this.nativeRolesStore = nativeRolesStore;
this.securityIndex = securityIndex;
}

@Override
protected void doExecute(Task task, QueryRoleRequest request, ActionListener<QueryRoleResponse> listener) {
final SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource()
.version(false)
.fetchSource(true)
.trackTotalHits(true);
SearchSourceBuilder searchSourceBuilder = SearchSourceBuilder.searchSource().version(false).fetchSource(true).trackTotalHits(true);
if (request.getFrom() != null) {
searchSourceBuilder.from(request.getFrom());
}
if (request.getSize() != null) {
searchSourceBuilder.size(request.getSize());
}
searchSourceBuilder.query(RoleBoolQueryBuilder.build(request.getQueryBuilder(), null));
AtomicBoolean isQueryingMetadata = new AtomicBoolean(false);
searchSourceBuilder.query(RoleBoolQueryBuilder.build(request.getQueryBuilder(), indexFieldName -> {
if (indexFieldName.startsWith(FieldNameTranslators.FLATTENED_METADATA_INDEX_FIELD_NAME)) {
isQueryingMetadata.set(true);
}
}));
if (isQueryingMetadata.get()) {
if (securityIndex.isMigrationsVersionAtLeast(ROLE_METADATA_FLATTENED_MIGRATION_VERSION) == false) {
listener.onFailure(
new ElasticsearchStatusException(
"Cannot query role metadata until automatic migration completed",
RestStatus.SERVICE_UNAVAILABLE
)
);
return;
}
}
if (request.getFieldSortBuilders() != null) {
ROLE_FIELD_NAME_TRANSLATORS.translateFieldSortBuilders(request.getFieldSortBuilders(), searchSourceBuilder, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@

public final class FieldNameTranslators {

public static final String FLATTENED_METADATA_INDEX_FIELD_NAME = "metadata_flattened";

public static final FieldNameTranslators API_KEY_FIELD_NAME_TRANSLATORS = new FieldNameTranslators(
List.of(
new SimpleFieldNameTranslator("creator.principal", "username"),
Expand All @@ -53,7 +55,7 @@ public final class FieldNameTranslators {
new SimpleFieldNameTranslator("invalidation_time", "invalidation"),
// allows querying on any non-wildcard sub-fields under the "metadata." prefix
// also allows querying on the "metadata" field itself (including by specifying patterns)
new FlattenedFieldNameTranslator("metadata_flattened", "metadata")
new FlattenedFieldNameTranslator(FLATTENED_METADATA_INDEX_FIELD_NAME, "metadata")
)
);

Expand All @@ -77,7 +79,7 @@ public final class FieldNameTranslators {
new SimpleFieldNameTranslator("applications.privileges", "applications.privilege"),
// allows querying on any non-wildcard sub-fields under the "metadata." prefix
// also allows querying on the "metadata" field itself (including by specifying patterns)
new FlattenedFieldNameTranslator("metadata_flattened", "metadata")
new FlattenedFieldNameTranslator(FLATTENED_METADATA_INDEX_FIELD_NAME, "metadata")
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ public boolean isStateRecovered() {
return this.state != State.UNRECOVERED_STATE;
}

public boolean isMigrationsVersionAtLeast(Integer expectedMigrationsVersion) {
return indexExists() && this.state.migrationsVersion.compareTo(expectedMigrationsVersion) >= 0;
}

public ElasticsearchException getUnavailableReason(Availability availability) {
// ensure usage of a local copy so all checks execute against the same state!
if (defensiveCopy == false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,55 +57,64 @@ public interface SecurityMigration {
int minMappingVersion();
}

public static final TreeMap<Integer, SecurityMigration> MIGRATIONS_BY_VERSION = new TreeMap<>(Map.of(1, new SecurityMigration() {
private static final Logger logger = LogManager.getLogger(SecurityMigration.class);
public static final Integer ROLE_METADATA_FLATTENED_MIGRATION_VERSION = 1;

@Override
public void migrate(SecurityIndexManager indexManager, Client client, ActionListener<Void> listener) {
BoolQueryBuilder filterQuery = new BoolQueryBuilder().filter(QueryBuilders.termQuery("type", "role"))
.mustNot(QueryBuilders.existsQuery("metadata_flattened"));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(filterQuery).size(0).trackTotalHits(true);
SearchRequest countRequest = new SearchRequest(indexManager.getConcreteIndexName());
countRequest.source(searchSourceBuilder);
public static final TreeMap<Integer, SecurityMigration> MIGRATIONS_BY_VERSION = new TreeMap<>(
Map.of(ROLE_METADATA_FLATTENED_MIGRATION_VERSION, new SecurityMigration() {
private static final Logger logger = LogManager.getLogger(SecurityMigration.class);

client.search(countRequest, ActionListener.wrap(response -> {
// If there are no roles, skip migration
if (response.getHits().getTotalHits().value > 0) {
logger.info("Preparing to migrate [" + response.getHits().getTotalHits().value + "] roles");
updateRolesByQuery(indexManager, client, filterQuery, listener);
} else {
listener.onResponse(null);
}
}, listener::onFailure));
}
@Override
public void migrate(SecurityIndexManager indexManager, Client client, ActionListener<Void> listener) {
BoolQueryBuilder filterQuery = new BoolQueryBuilder().filter(QueryBuilders.termQuery("type", "role"))
.mustNot(QueryBuilders.existsQuery("metadata_flattened"));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().query(filterQuery).size(0).trackTotalHits(true);
SearchRequest countRequest = new SearchRequest(indexManager.getConcreteIndexName());
countRequest.source(searchSourceBuilder);

private void updateRolesByQuery(
SecurityIndexManager indexManager,
Client client,
BoolQueryBuilder filterQuery,
ActionListener<Void> listener
) {
UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(indexManager.getConcreteIndexName());
updateByQueryRequest.setQuery(filterQuery);
updateByQueryRequest.setScript(
new Script(ScriptType.INLINE, "painless", "ctx._source.metadata_flattened = ctx._source.metadata", Collections.emptyMap())
);
client.admin()
.cluster()
.execute(UpdateByQueryAction.INSTANCE, updateByQueryRequest, ActionListener.wrap(bulkByScrollResponse -> {
logger.info("Migrated [" + bulkByScrollResponse.getTotal() + "] roles");
listener.onResponse(null);
client.search(countRequest, ActionListener.wrap(response -> {
// If there are no roles, skip migration
if (response.getHits().getTotalHits().value > 0) {
logger.info("Preparing to migrate [" + response.getHits().getTotalHits().value + "] roles");
updateRolesByQuery(indexManager, client, filterQuery, listener);
} else {
listener.onResponse(null);
}
}, listener::onFailure));
}
}

@Override
public Set<NodeFeature> nodeFeaturesRequired() {
return Set.of(SecuritySystemIndices.SECURITY_ROLES_METADATA_FLATTENED);
}
private void updateRolesByQuery(
SecurityIndexManager indexManager,
Client client,
BoolQueryBuilder filterQuery,
ActionListener<Void> listener
) {
UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest(indexManager.getConcreteIndexName());
updateByQueryRequest.setQuery(filterQuery);
updateByQueryRequest.setScript(
new Script(
ScriptType.INLINE,
"painless",
"ctx._source.metadata_flattened = ctx._source.metadata",
Collections.emptyMap()
)
);
client.admin()
.cluster()
.execute(UpdateByQueryAction.INSTANCE, updateByQueryRequest, ActionListener.wrap(bulkByScrollResponse -> {
logger.info("Migrated [" + bulkByScrollResponse.getTotal() + "] roles");
listener.onResponse(null);
}, listener::onFailure));
}

@Override
public int minMappingVersion() {
return ADD_REMOTE_CLUSTER_AND_DESCRIPTION_FIELDS.id();
}
}));
@Override
public Set<NodeFeature> nodeFeaturesRequired() {
return Set.of(SecuritySystemIndices.SECURITY_ROLES_METADATA_FLATTENED);
}

@Override
public int minMappingVersion() {
return ADD_REMOTE_CLUSTER_AND_DESCRIPTION_FIELDS.id();
}
})
);
}

0 comments on commit c5e6012

Please sign in to comment.