Skip to content

poc #129968

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft

poc #129968

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
22 changes: 22 additions & 0 deletions server/src/main/java/org/elasticsearch/FlatIndicesRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch;

import org.elasticsearch.action.IndicesRequest;

import java.util.List;

public interface FlatIndicesRequest extends IndicesRequest {
boolean requiresRewrite();

void indexExpressions(List<IndexExpression> indexExpressions);

record IndexExpression(String original, List<String> rewritten) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package org.elasticsearch.action.search;

import org.elasticsearch.FlatIndicesRequest;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;
Expand Down Expand Up @@ -53,7 +54,11 @@
* @see Client#search(SearchRequest)
* @see SearchResponse
*/
public class SearchRequest extends LegacyActionRequest implements IndicesRequest.Replaceable, Rewriteable<SearchRequest> {
public class SearchRequest extends LegacyActionRequest
implements
FlatIndicesRequest,
IndicesRequest.Replaceable,
Rewriteable<SearchRequest> {

public static final ToXContent.Params FORMAT_PARAMS = new ToXContent.MapParams(Collections.singletonMap("pretty", "false"));

Expand All @@ -70,6 +75,9 @@ public class SearchRequest extends LegacyActionRequest implements IndicesRequest

private String[] indices = Strings.EMPTY_ARRAY;

@Nullable
private List<IndexExpression> indexExpressions;

@Nullable
private String routing;
@Nullable
Expand Down Expand Up @@ -853,4 +861,16 @@ public String toString() {
+ source
+ '}';
}

@Override
public boolean requiresRewrite() {
return indexExpressions == null;
}

@Override
public void indexExpressions(List<IndexExpression> indexExpressions) {
assert requiresRewrite();
this.indexExpressions = indexExpressions;
indices(indexExpressions.stream().flatMap(indexExpression -> indexExpression.rewritten().stream()).toArray(String[]::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ interface AuthorizedIndices {
* Checks if an index-like resource name is authorized, for an action by a user. The resource might or might not exist.
*/
boolean check(String name, IndexComponentSelector selector);

// Does not belong here
default boolean checkProject(String projectId) {
return false;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/
package org.elasticsearch.xpack.security.authz;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.FlatIndicesRequest;
import org.elasticsearch.action.AliasesRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
Expand Down Expand Up @@ -55,6 +58,8 @@

class IndicesAndAliasesResolver {

private static final Logger logger = LogManager.getLogger(IndicesAndAliasesResolver.class);

private final IndexNameExpressionResolver nameExpressionResolver;
private final IndexAbstractionResolver indexAbstractionResolver;
private final RemoteClusterResolver remoteClusterResolver;
Expand Down Expand Up @@ -103,7 +108,6 @@ class IndicesAndAliasesResolver {
* resolving wildcards.
* </p>
*/

ResolvedIndices resolve(
String action,
TransportRequest request,
Expand All @@ -124,9 +128,62 @@ ResolvedIndices resolve(
if (request instanceof IndicesRequest == false) {
throw new IllegalStateException("Request [" + request + "] is not an Indices request, but should be.");
}

if (request instanceof FlatIndicesRequest flatIndicesRequest && flatIndicesRequest.requiresRewrite()) {
rewrite(flatIndicesRequest, authorizedIndices);
}

return resolveIndicesAndAliases(action, (IndicesRequest) request, projectMetadata, authorizedIndices);
}

void rewrite(FlatIndicesRequest request, AuthorizationEngine.AuthorizedIndices authorizedIndices) {
assert request.requiresRewrite();

var clusters = remoteClusterResolver.clusters();
logger.info("Clusters available for remote indices: {}", clusters);
// no remotes, nothing to rewrite...
if (clusters.isEmpty()) {
logger.info("No remote clusters linked, skipping...");
return;
}

var indices = request.indices();
// empty indices actually means search everything so would need to also rewrite that

var authorizedClusters = new HashSet<String>();
for (var cluster : clusters) {
if (authorizedIndices.checkProject(cluster)) {
logger.info("Remote cluster [{}] authorized", cluster);
authorizedClusters.add(cluster);
}
}

if (authorizedClusters.isEmpty()) {
logger.info("No remote clusters authorized, skipping...");
return;
}

ResolvedIndices resolved = remoteClusterResolver.splitLocalAndRemoteIndexNames(indices);
// skip handling searches where there's both qualified and flat expressions to simplify POC
// in the real thing, we'd also rewrite these
if (resolved.getRemote().isEmpty() == false) {
return;
}

List<FlatIndicesRequest.IndexExpression> indexExpressions = new ArrayList<>(indices.length);
for (var local : resolved.getLocal()) {
List<String> rewritten = new ArrayList<>();
rewritten.add(local);
for (var cluster : authorizedClusters) {
rewritten.add(RemoteClusterAware.buildRemoteIndexName(cluster, local));
indexExpressions.add(new FlatIndicesRequest.IndexExpression(local, rewritten));
}
logger.info("Rewrote [{}] to [{}]", local, rewritten);
}

request.indexExpressions(indexExpressions);
}

/**
* Attempt to resolve requested indices without expanding any wildcards.
* @return The {@link ResolvedIndices} or null if wildcard expansion must be performed.
Expand Down Expand Up @@ -569,5 +626,9 @@ ResolvedIndices splitLocalAndRemoteIndexNames(String... indices) {
.toList();
return new ResolvedIndices(local == null ? List.of() : local, remote);
}

Set<String> clusters() {
return Collections.unmodifiableSet(clusters);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,10 @@ static AuthorizedIndices resolveAuthorizedIndicesFromRole(
} // we don't support granting access to a backing index with a failure selector via the parent data stream
}
return predicate.test(indexAbstraction, selector);
}, name -> {
// just some bogus predicate that lets us differentiate between roles, not at all
// how this will work in the end
return Arrays.asList(role.names()).contains("_es_test_root");
});
}

Expand Down Expand Up @@ -1125,15 +1129,18 @@ static final class AuthorizedIndices implements AuthorizationEngine.AuthorizedIn
private final CachedSupplier<Set<String>> authorizedAndAvailableDataResources;
private final CachedSupplier<Set<String>> authorizedAndAvailableFailuresResources;
private final BiPredicate<String, IndexComponentSelector> isAuthorizedPredicate;
private final Predicate<String> projectPredicate;

AuthorizedIndices(
Supplier<Set<String>> authorizedAndAvailableDataResources,
Supplier<Set<String>> authorizedAndAvailableFailuresResources,
BiPredicate<String, IndexComponentSelector> isAuthorizedPredicate
BiPredicate<String, IndexComponentSelector> isAuthorizedPredicate,
Predicate<String> projectPredicate
) {
this.authorizedAndAvailableDataResources = CachedSupplier.wrap(authorizedAndAvailableDataResources);
this.authorizedAndAvailableFailuresResources = CachedSupplier.wrap(authorizedAndAvailableFailuresResources);
this.isAuthorizedPredicate = Objects.requireNonNull(isAuthorizedPredicate);
this.projectPredicate = projectPredicate;
}

@Override
Expand All @@ -1149,5 +1156,11 @@ public boolean check(String name, IndexComponentSelector selector) {
Objects.requireNonNull(selector, "must specify a selector for authorization check");
return isAuthorizedPredicate.test(name, selector);
}

@Override
public boolean checkProject(String name) {
Objects.requireNonNull(name, "must specify a project name for authorization check");
return projectPredicate.test(name);
}
}
}