Skip to content
Draft
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 @@ -19,7 +19,6 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.RemoteClusterAware;

import java.util.Map;
Expand Down Expand Up @@ -79,6 +78,8 @@ public static ElasticsearchException validate(
ResolvedIndexExpressions localResolvedExpressions,
Map<String, ResolvedIndexExpressions> remoteResolvedExpressions
) {
ElasticsearchException baseException = null;

if (indicesOptions.allowNoIndices() && indicesOptions.ignoreUnavailable()) {
logger.debug("Skipping index existence check in lenient mode");
return null;
Expand Down Expand Up @@ -106,20 +107,31 @@ public static ElasticsearchException validate(
if (isQualifiedExpression) {
ElasticsearchException e = checkResolutionFailure(localExpressions, result, originalExpression, indicesOptions);
if (e != null) {
return e;
baseException = recordException(baseException, e);
}
// qualified linked project expression
for (String remoteExpression : remoteExpressions) {
String[] splitResource = splitQualifiedResource(remoteExpression);
ElasticsearchException exception = checkSingleRemoteExpression(
var projectAlias = splitResource[0];
var resource = splitResource[1];

ElasticsearchException remoteException = checkSingleRemoteExpression(
remoteResolvedExpressions,
splitResource[0], // projectAlias
splitResource[1], // resource
projectAlias,
resource,
remoteExpression,
indicesOptions
);
if (exception != null) {
return exception;
if (remoteException != null) {
var wrapped = remoteException instanceof ElasticsearchSecurityException
? new ElasticsearchSecurityException(
"indices [{}] are unauthorized for project [{}]",
remoteException,
resource,
projectAlias
)
: remoteException;
baseException = recordException(baseException, wrapped);
}
}
} else {
Expand All @@ -138,33 +150,56 @@ public static ElasticsearchException validate(
// checking if flat expression matched remotely
for (String remoteExpression : remoteExpressions) {
String[] splitResource = splitQualifiedResource(remoteExpression);
ElasticsearchException exception = checkSingleRemoteExpression(
var projectAlias = splitResource[0];
var resource = splitResource[1];

ElasticsearchException remoteException = checkSingleRemoteExpression(
remoteResolvedExpressions,
splitResource[0], // projectAlias
splitResource[1], // resource
projectAlias,
resource,
remoteExpression,
indicesOptions
);
if (exception == null) {
if (remoteException == null) {
// found flat expression somewhere
foundFlat = true;
break;
}
if (false == isUnauthorized && exception instanceof ElasticsearchSecurityException) {
if (false == isUnauthorized && remoteException instanceof ElasticsearchSecurityException) {
isUnauthorized = true;
var wrapped = new ElasticsearchSecurityException(
"indices [{}] are unauthorized for project [{}]",
remoteException,
resource,
projectAlias
);
baseException = recordException(baseException, wrapped);
}
}
if (foundFlat) {
continue;
}
if (isUnauthorized) {
return localException;
baseException = recordException(baseException, localException);
continue;
}
return new IndexNotFoundException(originalExpression);
baseException = recordException(baseException, new IndexNotFoundException(originalExpression));
}
}
// if we didn't throw before it means that we can proceed with the request
return null;

return baseException;
}

private static ElasticsearchException recordException(
@Nullable ElasticsearchException baseException,
ElasticsearchException exception
) {
if (baseException == null) {
return exception;
} else {
baseException.addSuppressed(exception);
return baseException;
}
}

public static IndicesOptions indicesOptionsForCrossProjectFanout(IndicesOptions indicesOptions) {
Expand All @@ -175,11 +210,6 @@ public static IndicesOptions indicesOptionsForCrossProjectFanout(IndicesOptions
.build();
}

private static ElasticsearchSecurityException securityException(String originalExpression) {
// TODO plug in proper recorded authorization exceptions instead, once available
return new ElasticsearchSecurityException("user cannot access [" + originalExpression + "]", RestStatus.FORBIDDEN);
}

private static ElasticsearchException checkSingleRemoteExpression(
Map<String, ResolvedIndexExpressions> remoteResolvedExpressions,
String projectAlias,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import java.util.Map;
import java.util.Set;

import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;

Expand Down Expand Up @@ -269,11 +271,7 @@ public void testMissingQualifiedExpressionWithStrictIgnoreUnavailable() {
List.of(
new ResolvedIndexExpression(
original,
new ResolvedIndexExpression.LocalExpressions(
Set.of(),
ResolvedIndexExpression.LocalIndexResolutionResult.CONCRETE_RESOURCE_NOT_VISIBLE,
null
),
new ResolvedIndexExpression.LocalExpressions(Set.of(), ResolvedIndexExpression.LocalIndexResolutionResult.NONE, null),
Set.of("P1:logs")
)
)
Expand Down Expand Up @@ -318,7 +316,7 @@ public void testUnauthorizedQualifiedExpressionWithStrictIgnoreUnavailable() {
)
);

final var exception = new ElasticsearchException("action is unauthorized for indices [logs]");
final var exception = new ElasticsearchSecurityException("action is unauthorized for indices [logs]");
var remote = Map.of(
"P1",
new ResolvedIndexExpressions(
Expand All @@ -343,7 +341,8 @@ public void testUnauthorizedQualifiedExpressionWithStrictIgnoreUnavailable() {
remote
);
assertNotNull(e);
assertThat(e, is(exception));
assertThat(e.getMessage(), equalTo("indices [logs] are unauthorized for project [P1]"));
assertThat(e.getCause(), is(exception));
}

public void testFlatExpressionWithStrictAllowNoIndicesMatchingInOriginProject() {
Expand All @@ -365,7 +364,7 @@ public void testFlatExpressionWithStrictAllowNoIndicesMatchingInOriginProject()
assertNull(CrossProjectIndexResolutionValidator.validate(getStrictAllowNoIndices(), null, local, null));
}

public void testAllowNoIndicesFoundEmptyResultsOnOriginAndLinked() {
public void testStrictAllowNoIndicesFoundEmptyResultsOnOriginAndLinked() {
ResolvedIndexExpressions local = new ResolvedIndexExpressions(
List.of(
new ResolvedIndexExpression(
Expand Down Expand Up @@ -668,6 +667,8 @@ public void testMissingQualifiedExpressionWithStrictAllowNoIndices() {
assertNotNull(e);
assertThat(e, instanceOf(IndexNotFoundException.class));
assertThat(e.getMessage(), containsString("no such index [" + original + "]"));
assertThat(e.getSuppressed(), arrayWithSize(1));
assertThat(e.getSuppressed()[0].getMessage(), equalTo("no such index [P1:logs*]"));
}

public void testUnauthorizedQualifiedExpressionWithStrictAllowNoIndices() {
Expand All @@ -676,11 +677,7 @@ public void testUnauthorizedQualifiedExpressionWithStrictAllowNoIndices() {
List.of(
new ResolvedIndexExpression(
original,
new ResolvedIndexExpression.LocalExpressions(
Set.of(),
ResolvedIndexExpression.LocalIndexResolutionResult.SUCCESS,
null
),
new ResolvedIndexExpression.LocalExpressions(Set.of(), ResolvedIndexExpression.LocalIndexResolutionResult.NONE, null),
Set.of("P1:logs*")
)
)
Expand Down Expand Up @@ -710,7 +707,7 @@ public void testUnauthorizedQualifiedExpressionWithStrictAllowNoIndices() {
);
assertNotNull(e);
assertThat(e, instanceOf(IndexNotFoundException.class));
assertThat(e.getMessage(), containsString("no such index [" + original + "]"));
assertThat(e.getMessage(), containsString("no such index [P1:logs*]"));
}

private IndicesOptions getStrictAllowNoIndices() {
Expand Down