Consolidate use of RemoteAccessHeaders (#93662)

This refactor PR extracts the RemoteAccessHeaders class and re-uses it
between querying-cluster and fulfilling-cluster side code.
This commit is contained in:
Nikolaj Volgushev 2023-02-13 21:23:58 +01:00 committed by GitHub
parent ee56ea8c82
commit 7d88d13312
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 328 additions and 91 deletions

View file

@ -56,7 +56,11 @@ public final class RemoteAccessAuthentication {
} }
public static RemoteAccessAuthentication readFromContext(final ThreadContext ctx) throws IOException { public static RemoteAccessAuthentication readFromContext(final ThreadContext ctx) throws IOException {
return decode(ctx.getHeader(REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY)); final String header = ctx.getHeader(REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY);
if (header == null) {
throw new IllegalArgumentException("remote access header [" + REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY + "] is required");
}
return decode(header);
} }
public Authentication getAuthentication() { public Authentication getAuthentication() {
@ -212,5 +216,23 @@ public final class RemoteAccessAuthentication {
public String toString() { public String toString() {
return "RoleDescriptorsBytes{" + "rawBytes=" + rawBytes + '}'; return "RoleDescriptorsBytes{" + "rawBytes=" + rawBytes + '}';
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (false == super.equals(o)) return false;
RoleDescriptorsBytes that = (RoleDescriptorsBytes) o;
return Objects.equals(rawBytes, that.rawBytes);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (rawBytes != null ? rawBytes.hashCode() : 0);
return result;
}
} }
} }

View file

@ -23,6 +23,7 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY;
import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomUniquelyNamedRoleDescriptors; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomUniquelyNamedRoleDescriptors;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
@ -58,6 +59,14 @@ public class RemoteAccessAuthenticationTests extends ESTestCase {
assertThat(actualRoleDescriptors, equalTo(expectedRoleDescriptors)); assertThat(actualRoleDescriptors, equalTo(expectedRoleDescriptors));
} }
public void testThrowsOnMissingEntry() {
var actual = expectThrows(
IllegalArgumentException.class,
() -> RemoteAccessAuthentication.readFromContext(new ThreadContext(Settings.EMPTY))
);
assertThat(actual.getMessage(), equalTo("remote access header [" + REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY + "] is required"));
}
private RoleDescriptorsIntersection randomRoleDescriptorsIntersection() { private RoleDescriptorsIntersection randomRoleDescriptorsIntersection() {
return new RoleDescriptorsIntersection(randomList(0, 3, () -> Set.copyOf(randomUniquelyNamedRoleDescriptors(0, 1)))); return new RoleDescriptorsIntersection(randomList(0, 3, () -> Set.copyOf(randomUniquelyNamedRoleDescriptors(0, 1))));
} }

View file

@ -183,6 +183,7 @@ public class RemoteClusterSecurityRestIT extends AbstractRemoteClusterSecurityTe
return client().performRequest(request); return client().performRequest(request);
} }
// TODO centralize common usage of this across all tests
private static String randomEncodedApiKey() { private static String randomEncodedApiKey() {
return Base64.getEncoder().encodeToString((UUIDs.base64UUID() + ":" + UUIDs.base64UUID()).getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString((UUIDs.base64UUID() + ":" + UUIDs.base64UUID()).getBytes(StandardCharsets.UTF_8));
} }

View file

@ -53,8 +53,8 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.SecurityOnTrialLicenseRestTestCase; import org.elasticsearch.xpack.security.SecurityOnTrialLicenseRestTestCase;
import org.elasticsearch.xpack.security.authc.RemoteAccessHeaders;
import org.elasticsearch.xpack.security.authz.RBACEngine; import org.elasticsearch.xpack.security.authz.RBACEngine;
import org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -393,9 +393,9 @@ public class RemoteAccessHeadersForCcsRestIT extends SecurityOnTrialLicenseRestT
} }
private void assertContainsRemoteClusterCredential(String clusterCredential, CapturedActionWithHeaders actual) { private void assertContainsRemoteClusterCredential(String clusterCredential, CapturedActionWithHeaders actual) {
assertThat(actual.headers(), hasKey(SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY)); assertThat(actual.headers(), hasKey(RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY));
assertThat( assertThat(
actual.headers().get(SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY), actual.headers().get(RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY),
equalTo("ApiKey " + clusterCredential) equalTo("ApiKey " + clusterCredential)
); );
} }
@ -480,7 +480,7 @@ public class RemoteAccessHeadersForCcsRestIT extends SecurityOnTrialLicenseRestT
actualHeaders.keySet(), actualHeaders.keySet(),
containsInAnyOrder( containsInAnyOrder(
RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY, RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY,
SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY
) )
); );
} }

View file

@ -980,12 +980,15 @@ public class ApiKeyService {
return getCredentialsFromHeader(threadContext.getHeader("Authorization")); return getCredentialsFromHeader(threadContext.getHeader("Authorization"));
} }
ApiKeyCredentials getCredentialsFromHeader(final String header) { static ApiKeyCredentials getCredentialsFromHeader(final String header) {
assert isEnabled() : "API keys must be enabled";
return parseApiKey(Authenticator.extractCredentialFromHeaderValue(header, "ApiKey")); return parseApiKey(Authenticator.extractCredentialFromHeaderValue(header, "ApiKey"));
} }
private ApiKeyCredentials parseApiKey(SecureString apiKeyString) { public static String withApiKeyPrefix(final String encodedApiKey) {
return "ApiKey " + encodedApiKey;
}
private static ApiKeyCredentials parseApiKey(SecureString apiKeyString) {
if (apiKeyString != null) { if (apiKeyString != null) {
final byte[] decodedApiKeyCredBytes = Base64.getDecoder().decode(CharArrays.toUtf8Bytes(apiKeyString.getChars())); final byte[] decodedApiKeyCredBytes = Base64.getDecoder().decode(CharArrays.toUtf8Bytes(apiKeyString.getChars()));
char[] apiKeyCredChars = null; char[] apiKeyCredChars = null;
@ -1087,6 +1090,7 @@ public class ApiKeyService {
public void clearCredentials() { public void clearCredentials() {
close(); close();
} }
} }
private static class ApiKeyLoggingDeprecationHandler implements DeprecationHandler { private static class ApiKeyLoggingDeprecationHandler implements DeprecationHandler {

View file

@ -34,7 +34,7 @@ import java.util.function.Supplier;
import static org.elasticsearch.core.Strings.format; import static org.elasticsearch.core.Strings.format;
import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY; import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY;
import static org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY; import static org.elasticsearch.xpack.security.authc.RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY;
public class RemoteAccessAuthenticationService { public class RemoteAccessAuthenticationService {
@ -87,7 +87,8 @@ public class RemoteAccessAuthenticationService {
final RemoteAccessHeaders remoteAccessHeaders; final RemoteAccessHeaders remoteAccessHeaders;
try { try {
remoteAccessHeaders = extractRemoteAccessHeaders(threadContext); apiKeyService.ensureEnabled();
remoteAccessHeaders = RemoteAccessHeaders.readFromContext(threadContext);
} catch (Exception ex) { } catch (Exception ex) {
withRequestProcessingFailure(authcContext, ex, listener); withRequestProcessingFailure(authcContext, ex, listener);
return; return;
@ -104,7 +105,7 @@ public class RemoteAccessAuthenticationService {
) )
) { ) {
final Supplier<ThreadContext.StoredContext> storedContextSupplier = threadContext.newRestorableContext(false); final Supplier<ThreadContext.StoredContext> storedContextSupplier = threadContext.newRestorableContext(false);
authcContext.addAuthenticationToken(remoteAccessHeaders.clusterCredential()); authcContext.addAuthenticationToken(remoteAccessHeaders.clusterCredentials());
authenticationService.authenticate( authenticationService.authenticate(
authcContext, authcContext,
new ContextPreservingActionListener<>(storedContextSupplier, ActionListener.wrap(authentication -> { new ContextPreservingActionListener<>(storedContextSupplier, ActionListener.wrap(authentication -> {
@ -122,7 +123,7 @@ public class RemoteAccessAuthenticationService {
} }
} }
private static RemoteAccessAuthentication maybeRewriteForSystemUser(RemoteAccessAuthentication remoteAccessAuthentication) private static RemoteAccessAuthentication maybeRewriteForSystemUser(final RemoteAccessAuthentication remoteAccessAuthentication)
throws IOException { throws IOException {
final Subject receivedEffectiveSubject = remoteAccessAuthentication.getAuthentication().getEffectiveSubject(); final Subject receivedEffectiveSubject = remoteAccessAuthentication.getAuthentication().getEffectiveSubject();
final User user = receivedEffectiveSubject.getUser(); final User user = receivedEffectiveSubject.getUser();
@ -148,29 +149,6 @@ public class RemoteAccessAuthenticationService {
return authenticationService; return authenticationService;
} }
private record RemoteAccessHeaders(
ApiKeyService.ApiKeyCredentials clusterCredential,
RemoteAccessAuthentication remoteAccessAuthentication
) {}
private RemoteAccessHeaders extractRemoteAccessHeaders(final ThreadContext threadContext) throws IOException {
apiKeyService.ensureEnabled();
final String clusterCredentialHeader = threadContext.getHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY);
if (clusterCredentialHeader == null) {
throw new IllegalArgumentException("remote access header [" + REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY + "] is required");
}
final ApiKeyService.ApiKeyCredentials apiKeyCredential = apiKeyService.getCredentialsFromHeader(clusterCredentialHeader);
if (apiKeyCredential == null) {
throw new IllegalArgumentException(
"remote access header [" + REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY + "] value must be a valid API key credential"
);
}
if (threadContext.getHeader(REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY) == null) {
throw new IllegalArgumentException("remote access header [" + REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY + "] is required");
}
return new RemoteAccessHeaders(apiKeyCredential, RemoteAccessAuthentication.readFromContext(threadContext));
}
private void validate(final RemoteAccessAuthentication remoteAccessAuthentication) { private void validate(final RemoteAccessAuthentication remoteAccessAuthentication) {
final Subject effectiveSubject = remoteAccessAuthentication.getAuthentication().getEffectiveSubject(); final Subject effectiveSubject = remoteAccessAuthentication.getAuthentication().getEffectiveSubject();
for (RemoteAccessAuthentication.RoleDescriptorsBytes roleDescriptorsBytes : remoteAccessAuthentication for (RemoteAccessAuthentication.RoleDescriptorsBytes roleDescriptorsBytes : remoteAccessAuthentication

View file

@ -0,0 +1,76 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authc;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication;
import java.io.IOException;
import java.util.Objects;
public final class RemoteAccessHeaders {
public static final String REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY = "_remote_access_cluster_credential";
private final String clusterCredentialsHeader;
private final RemoteAccessAuthentication remoteAccessAuthentication;
public RemoteAccessHeaders(String clusterCredentialsHeader, RemoteAccessAuthentication remoteAccessAuthentication) {
assert clusterCredentialsHeader.startsWith("ApiKey ") : "cluster credentials headers must start with [ApiKey ]";
this.clusterCredentialsHeader = clusterCredentialsHeader;
this.remoteAccessAuthentication = remoteAccessAuthentication;
}
public void writeToContext(final ThreadContext ctx) throws IOException {
ctx.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, clusterCredentialsHeader);
remoteAccessAuthentication.writeToContext(ctx);
}
public static RemoteAccessHeaders readFromContext(final ThreadContext ctx) throws IOException {
final String clusterCredentialsHeader = ctx.getHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY);
if (clusterCredentialsHeader == null) {
throw new IllegalArgumentException("remote access header [" + REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY + "] is required");
}
// Invoke parsing logic to validate that the header decodes to a valid API key credential
// Call `close` since the returned value is an auto-closable
parseClusterCredentialsHeader(clusterCredentialsHeader).close();
return new RemoteAccessHeaders(clusterCredentialsHeader, RemoteAccessAuthentication.readFromContext(ctx));
}
public ApiKeyService.ApiKeyCredentials clusterCredentials() {
return parseClusterCredentialsHeader(clusterCredentialsHeader);
}
private static ApiKeyService.ApiKeyCredentials parseClusterCredentialsHeader(final String header) {
try {
return Objects.requireNonNull(ApiKeyService.getCredentialsFromHeader(header));
} catch (Exception ex) {
throw new IllegalArgumentException(
"remote access header [" + REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY + "] value must be a valid API key credential",
ex
);
}
}
public RemoteAccessAuthentication remoteAccessAuthentication() {
return remoteAccessAuthentication;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (RemoteAccessHeaders) obj;
return Objects.equals(this.clusterCredentialsHeader, that.clusterCredentialsHeader)
&& Objects.equals(this.remoteAccessAuthentication, that.remoteAccessAuthentication);
}
@Override
public int hashCode() {
return Objects.hash(clusterCredentialsHeader, remoteAccessAuthentication);
}
}

View file

@ -21,7 +21,7 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY; import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY;
import static org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY; import static org.elasticsearch.xpack.security.authc.RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY;
final class RemoteAccessServerTransportFilter extends ServerTransportFilter { final class RemoteAccessServerTransportFilter extends ServerTransportFilter {
// pkg-private for testing // pkg-private for testing

View file

@ -14,6 +14,7 @@ import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
import org.elasticsearch.transport.TcpTransport; import org.elasticsearch.transport.TcpTransport;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import java.util.Map; import java.util.Map;
@ -47,7 +48,8 @@ public class RemoteClusterAuthorizationResolver {
public String resolveAuthorization(final String clusterAlias) { public String resolveAuthorization(final String clusterAlias) {
if (TcpTransport.isUntrustedRemoteClusterEnabled()) { if (TcpTransport.isUntrustedRemoteClusterEnabled()) {
return this.apiKeys.get(clusterAlias); final String apiKey = apiKeys.get(clusterAlias);
return apiKey == null ? null : ApiKeyService.withApiKeyPrefix(apiKey);
} }
return null; return null;
} }

View file

@ -49,6 +49,7 @@ import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.RemoteAccessAuthenticationService; import org.elasticsearch.xpack.security.authc.RemoteAccessAuthenticationService;
import org.elasticsearch.xpack.security.authc.RemoteAccessHeaders;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.AuthorizationUtils; import org.elasticsearch.xpack.security.authz.AuthorizationUtils;
import org.elasticsearch.xpack.security.authz.PreAuthorizationUtils; import org.elasticsearch.xpack.security.authz.PreAuthorizationUtils;
@ -67,7 +68,6 @@ import static org.elasticsearch.transport.RemoteClusterPortSettings.REMOTE_CLUST
public class SecurityServerTransportInterceptor implements TransportInterceptor { public class SecurityServerTransportInterceptor implements TransportInterceptor {
public static final String REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY = "_remote_access_cluster_credential";
private static final TransportVersion VERSION_REMOTE_ACCESS_HEADERS = TransportVersion.V_8_7_0; private static final TransportVersion VERSION_REMOTE_ACCESS_HEADERS = TransportVersion.V_8_7_0;
private static final Logger logger = LogManager.getLogger(SecurityServerTransportInterceptor.class); private static final Logger logger = LogManager.getLogger(SecurityServerTransportInterceptor.class);
// package private for testing // package private for testing
@ -196,7 +196,7 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
} }
private void assertNoRemoteAccessHeadersInContext() { private void assertNoRemoteAccessHeadersInContext() {
assert securityContext.getThreadContext().getHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY) == null assert securityContext.getThreadContext().getHeader(RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY) == null
: "remote access headers should not be in security context"; : "remote access headers should not be in security context";
assert securityContext.getThreadContext() assert securityContext.getThreadContext()
.getHeader(RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY) == null .getHeader(RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY) == null
@ -366,11 +366,13 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
final User user = authentication.getEffectiveSubject().getUser(); final User user = authentication.getEffectiveSubject().getUser();
if (SystemUser.is(user)) { if (SystemUser.is(user)) {
try (ThreadContext.StoredContext ignored = threadContext.stashContext()) { try (ThreadContext.StoredContext ignored = threadContext.stashContext()) {
remoteAccessCredentials.writeToContext(threadContext); new RemoteAccessHeaders(
// Access control is handled differently for the system user. Privileges are defined by the fulfilling cluster, remoteAccessCredentials.credentials(),
// so we pass an empty role descriptors intersection here and let the receiver resolve privileges based on the // Access control is handled differently for the system user. Privileges are defined by the fulfilling cluster,
// authentication instance // so we pass an empty role descriptors intersection here and let the receiver resolve privileges based on the
new RemoteAccessAuthentication(authentication, RoleDescriptorsIntersection.EMPTY).writeToContext(threadContext); // authentication instance
new RemoteAccessAuthentication(authentication, RoleDescriptorsIntersection.EMPTY)
).writeToContext(threadContext);
sender.sendRequest(connection, action, request, options, contextRestoreHandler); sender.sendRequest(connection, action, request, options, contextRestoreHandler);
} catch (IOException e) { } catch (IOException e) {
contextRestoreHandler.handleException(new SendRequestTransportException(connection.getNode(), action, e)); contextRestoreHandler.handleException(new SendRequestTransportException(connection.getNode(), action, e));
@ -385,8 +387,10 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
authentication.getEffectiveSubject(), authentication.getEffectiveSubject(),
ActionListener.wrap(roleDescriptorsIntersection -> { ActionListener.wrap(roleDescriptorsIntersection -> {
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
remoteAccessCredentials.writeToContext(threadContext); new RemoteAccessHeaders(
new RemoteAccessAuthentication(authentication, roleDescriptorsIntersection).writeToContext(threadContext); remoteAccessCredentials.credentials(),
new RemoteAccessAuthentication(authentication, roleDescriptorsIntersection)
).writeToContext(threadContext);
sender.sendRequest(connection, action, request, options, contextRestoreHandler); sender.sendRequest(connection, action, request, options, contextRestoreHandler);
} }
}, e -> contextRestoreHandler.handleException(new SendRequestTransportException(connection.getNode(), action, e))) }, e -> contextRestoreHandler.handleException(new SendRequestTransportException(connection.getNode(), action, e)))
@ -394,15 +398,8 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
} }
} }
record RemoteAccessCredentials(String clusterAlias, String credentials) { // TODO move this to `RemoteClusterAuthorizationResolver` and have `resolveAuthorization` return it
void writeToContext(final ThreadContext ctx) { record RemoteAccessCredentials(String clusterAlias, String credentials) {}
ctx.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, withApiKeyPrefix(credentials));
}
private String withApiKeyPrefix(final String clusterCredential) {
return "ApiKey " + clusterCredential;
}
}
}; };
} }

View file

@ -0,0 +1,114 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authc;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Set;
import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomUniquelyNamedRoleDescriptors;
import static org.elasticsearch.xpack.security.authc.RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY;
import static org.hamcrest.Matchers.equalTo;
public class RemoteAccessHeadersTests extends ESTestCase {
public void testWriteReadContextRoundtrip() throws IOException {
final ThreadContext ctx = new ThreadContext(Settings.EMPTY);
final var expected = new RemoteAccessHeaders(
randomEncodedApiKeyHeader(),
AuthenticationTestHelper.randomRemoteAccessAuthentication(randomRoleDescriptorsIntersection())
);
expected.writeToContext(ctx);
final RemoteAccessHeaders actual = RemoteAccessHeaders.readFromContext(ctx);
assertThat(actual.remoteAccessAuthentication(), equalTo(expected.remoteAccessAuthentication()));
assertThat(actual.clusterCredentials().getId(), equalTo(expected.clusterCredentials().getId()));
assertThat(actual.clusterCredentials().getKey().toString(), equalTo(expected.clusterCredentials().getKey().toString()));
}
public void testClusterCredentialsReturnsValidApiKey() {
final String id = UUIDs.randomBase64UUID();
final String key = UUIDs.randomBase64UUID();
final String encodedApiKey = encodedApiKeyWithPrefix(id, key);
final var headers = new RemoteAccessHeaders(
encodedApiKey,
AuthenticationTestHelper.randomRemoteAccessAuthentication(randomRoleDescriptorsIntersection())
);
final ApiKeyService.ApiKeyCredentials actual = headers.clusterCredentials();
assertThat(actual.getId(), equalTo(id));
assertThat(actual.getKey().toString(), equalTo(key));
}
public void testReadOnInvalidApiKeyValueThrows() throws IOException {
final ThreadContext ctx = new ThreadContext(Settings.EMPTY);
final var expected = new RemoteAccessHeaders(
randomFrom("ApiKey abc", "ApiKey id:key", "ApiKey ", "ApiKey "),
AuthenticationTestHelper.randomRemoteAccessAuthentication(randomRoleDescriptorsIntersection())
);
expected.writeToContext(ctx);
var actual = expectThrows(IllegalArgumentException.class, () -> RemoteAccessHeaders.readFromContext(ctx));
assertThat(
actual.getMessage(),
equalTo("remote access header [" + REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY + "] value must be a valid API key credential")
);
}
public void testReadOnHeaderWithMalformedPrefixThrows() throws IOException {
final ThreadContext ctx = new ThreadContext(Settings.EMPTY);
AuthenticationTestHelper.randomRemoteAccessAuthentication(randomRoleDescriptorsIntersection()).writeToContext(ctx);
final String encodedApiKey = encodedApiKey(UUIDs.randomBase64UUID(), UUIDs.randomBase64UUID());
ctx.putHeader(
REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY,
randomFrom(
// missing space
"ApiKey" + encodedApiKey,
// no prefix
encodedApiKey,
// wrong prefix
"Bearer " + encodedApiKey
)
);
var actual = expectThrows(IllegalArgumentException.class, () -> RemoteAccessHeaders.readFromContext(ctx));
assertThat(
actual.getMessage(),
equalTo("remote access header [" + REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY + "] value must be a valid API key credential")
);
}
private static RoleDescriptorsIntersection randomRoleDescriptorsIntersection() {
return new RoleDescriptorsIntersection(randomList(0, 3, () -> Set.copyOf(randomUniquelyNamedRoleDescriptors(0, 1))));
}
// TODO centralize common usage of this across all tests
private static String randomEncodedApiKeyHeader() {
return encodedApiKeyWithPrefix(UUIDs.randomBase64UUID(), UUIDs.randomBase64UUID());
}
private static String encodedApiKeyWithPrefix(String id, String key) {
return "ApiKey " + encodedApiKey(id, key);
}
private static String encodedApiKey(String id, String key) {
return Base64.getEncoder().encodeToString((id + ":" + key).getBytes(StandardCharsets.UTF_8));
}
}

View file

@ -26,7 +26,9 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests; import org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection; import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.SystemUser;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.elasticsearch.xpack.security.authc.RemoteAccessAuthenticationService; import org.elasticsearch.xpack.security.authc.RemoteAccessAuthenticationService;
import org.elasticsearch.xpack.security.authc.RemoteAccessHeaders;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import java.io.IOException; import java.io.IOException;
@ -38,7 +40,7 @@ import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY; import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY;
import static org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY; import static org.elasticsearch.xpack.security.authc.RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
@ -65,7 +67,8 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
} }
try (var ignored = threadContext.stashContext()) { try (var ignored = threadContext.stashContext()) {
threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, "abc"); new RemoteAccessHeaders(ApiKeyService.withApiKeyPrefix("abc"), AuthenticationTestHelper.randomRemoteAccessAuthentication())
.writeToContext(threadContext);
authenticateAndAssertExpectedErrorMessage( authenticateAndAssertExpectedErrorMessage(
service, service,
msg -> assertThat( msg -> assertThat(
@ -82,7 +85,7 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
try (var ignored = threadContext.stashContext()) { try (var ignored = threadContext.stashContext()) {
final String randomApiKey = Base64.getEncoder() final String randomApiKey = Base64.getEncoder()
.encodeToString((UUIDs.base64UUID() + ":" + UUIDs.base64UUID()).getBytes(StandardCharsets.UTF_8)); .encodeToString((UUIDs.base64UUID() + ":" + UUIDs.base64UUID()).getBytes(StandardCharsets.UTF_8));
threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, "ApiKey " + randomApiKey); threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, ApiKeyService.withApiKeyPrefix(randomApiKey));
authenticateAndAssertExpectedErrorMessage( authenticateAndAssertExpectedErrorMessage(
service, service,
msg -> assertThat(msg, equalTo("remote access header [" + REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY + "] is required")) msg -> assertThat(msg, equalTo("remote access header [" + REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY + "] is required"))
@ -90,11 +93,13 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
} }
try (var ignored = threadContext.stashContext()) { try (var ignored = threadContext.stashContext()) {
threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, "ApiKey " + encodedRemoteAccessApiKey);
final var internalUser = randomValueOtherThan(SystemUser.INSTANCE, AuthenticationTestHelper::randomInternalUser); final var internalUser = randomValueOtherThan(SystemUser.INSTANCE, AuthenticationTestHelper::randomInternalUser);
new RemoteAccessAuthentication( new RemoteAccessHeaders(
AuthenticationTestHelper.builder().internal(internalUser).build(), encodedRemoteAccessApiKey,
RoleDescriptorsIntersection.EMPTY new RemoteAccessAuthentication(
AuthenticationTestHelper.builder().internal(internalUser).build(),
RoleDescriptorsIntersection.EMPTY
)
).writeToContext(threadContext); ).writeToContext(threadContext);
authenticateAndAssertExpectedErrorMessage( authenticateAndAssertExpectedErrorMessage(
service, service,
@ -106,16 +111,18 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
} }
try (var ignored = threadContext.stashContext()) { try (var ignored = threadContext.stashContext()) {
threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, "ApiKey " + encodedRemoteAccessApiKey); new RemoteAccessHeaders(
AuthenticationTestHelper.randomRemoteAccessAuthentication( encodedRemoteAccessApiKey,
new RoleDescriptorsIntersection( AuthenticationTestHelper.randomRemoteAccessAuthentication(
randomValueOtherThanMany( new RoleDescriptorsIntersection(
rd -> false == (rd.hasClusterPrivileges() randomValueOtherThanMany(
|| rd.hasApplicationPrivileges() rd -> false == (rd.hasClusterPrivileges()
|| rd.hasConfigurableClusterPrivileges() || rd.hasApplicationPrivileges()
|| rd.hasRunAs() || rd.hasConfigurableClusterPrivileges()
|| rd.hasRemoteIndicesPrivileges()), || rd.hasRunAs()
() -> RoleDescriptorTests.randomRoleDescriptor() || rd.hasRemoteIndicesPrivileges()),
() -> RoleDescriptorTests.randomRoleDescriptor()
)
) )
) )
).writeToContext(threadContext); ).writeToContext(threadContext);
@ -131,9 +138,12 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
} }
try (var ignored = threadContext.stashContext()) { try (var ignored = threadContext.stashContext()) {
threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, "ApiKey " + encodedRemoteAccessApiKey);
Authentication authentication = AuthenticationTestHelper.builder().apiKey().build(); Authentication authentication = AuthenticationTestHelper.builder().apiKey().build();
new RemoteAccessAuthentication(authentication, RoleDescriptorsIntersection.EMPTY).writeToContext(threadContext); new RemoteAccessHeaders(
encodedRemoteAccessApiKey,
new RemoteAccessAuthentication(authentication, RoleDescriptorsIntersection.EMPTY)
).writeToContext(threadContext);
authenticateAndAssertExpectedErrorMessage( authenticateAndAssertExpectedErrorMessage(
service, service,
msg -> assertThat( msg -> assertThat(
@ -151,16 +161,17 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
} }
public void testSystemUserIsMappedToCrossClusterInternalRole() throws InterruptedException, IOException, ExecutionException { public void testSystemUserIsMappedToCrossClusterInternalRole() throws InterruptedException, IOException, ExecutionException {
final String encodedRemoteAccessApiKey = getEncodedRemoteAccessApiKey();
final String nodeName = internalCluster().getRandomNodeName(); final String nodeName = internalCluster().getRandomNodeName();
final ThreadContext threadContext = internalCluster().getInstance(SecurityContext.class, nodeName).getThreadContext(); final ThreadContext threadContext = internalCluster().getInstance(SecurityContext.class, nodeName).getThreadContext();
final RemoteAccessAuthenticationService service = internalCluster().getInstance(RemoteAccessAuthenticationService.class, nodeName); final RemoteAccessAuthenticationService service = internalCluster().getInstance(RemoteAccessAuthenticationService.class, nodeName);
try (var ignored = threadContext.stashContext()) { try (var ignored = threadContext.stashContext()) {
threadContext.putHeader(REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY, "ApiKey " + encodedRemoteAccessApiKey); new RemoteAccessHeaders(
new RemoteAccessAuthentication( getEncodedRemoteAccessApiKey(),
AuthenticationTestHelper.builder().internal(SystemUser.INSTANCE).build(), new RemoteAccessAuthentication(
new RoleDescriptorsIntersection(new RoleDescriptor("role", null, null, null, null, null, null, null)) AuthenticationTestHelper.builder().internal(SystemUser.INSTANCE).build(),
new RoleDescriptorsIntersection(new RoleDescriptor("role", null, null, null, null, null, null, null))
)
).writeToContext(threadContext); ).writeToContext(threadContext);
final PlainActionFuture<Authentication> future = new PlainActionFuture<>(); final PlainActionFuture<Authentication> future = new PlainActionFuture<>();
@ -181,7 +192,9 @@ public class RemoteAccessAuthenticationServiceIntegTests extends SecurityIntegTe
private String getEncodedRemoteAccessApiKey() { private String getEncodedRemoteAccessApiKey() {
final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client().admin().cluster()).setName("remote_access_key").get(); final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client().admin().cluster()).setName("remote_access_key").get();
return Base64.getEncoder().encodeToString((response.getId() + ":" + response.getKey()).getBytes(StandardCharsets.UTF_8)); return ApiKeyService.withApiKeyPrefix(
Base64.getEncoder().encodeToString((response.getId() + ":" + response.getKey()).getBytes(StandardCharsets.UTF_8))
);
} }
private void authenticateAndAssertExpectedErrorMessage( private void authenticateAndAssertExpectedErrorMessage(

View file

@ -21,6 +21,7 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TcpTransport; import org.elasticsearch.transport.TcpTransport;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -68,7 +69,10 @@ public class RemoteClusterAuthorizationResolverTests extends ESTestCase {
initialSettings, initialSettings,
this.clusterService.getClusterSettings() this.clusterService.getClusterSettings()
); );
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA), is(equalTo("initialize"))); assertThat(
remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA),
is(equalTo(ApiKeyService.withApiKeyPrefix("initialize")))
);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB), is(nullValue())); assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB), is(nullValue()));
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue())); assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue()));
final DiscoveryNode masterNodeA = this.clusterService.state().nodes().getMasterNode(); final DiscoveryNode masterNodeA = this.clusterService.state().nodes().getMasterNode();
@ -81,8 +85,14 @@ public class RemoteClusterAuthorizationResolverTests extends ESTestCase {
.build(); .build();
final ClusterState newClusterState1 = createClusterState(clusterNameA, masterNodeA, newSettingsAddClusterB); final ClusterState newClusterState1 = createClusterState(clusterNameA, masterNodeA, newSettingsAddClusterB);
ClusterServiceUtils.setState(this.clusterService, newClusterState1); ClusterServiceUtils.setState(this.clusterService, newClusterState1);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA), is(equalTo("addB"))); assertThat(
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB), is(equalTo(clusterBapiKey1))); remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA),
is(equalTo(ApiKeyService.withApiKeyPrefix("addB")))
);
assertThat(
remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB),
is(equalTo(ApiKeyService.withApiKeyPrefix(clusterBapiKey1)))
);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue())); assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue()));
// Change clusterB authorization setting // Change clusterB authorization setting
@ -93,8 +103,14 @@ public class RemoteClusterAuthorizationResolverTests extends ESTestCase {
.build(); .build();
final ClusterState newClusterState2 = createClusterState(clusterNameA, masterNodeA, newSettingsUpdateClusterB); final ClusterState newClusterState2 = createClusterState(clusterNameA, masterNodeA, newSettingsUpdateClusterB);
ClusterServiceUtils.setState(this.clusterService, newClusterState2); ClusterServiceUtils.setState(this.clusterService, newClusterState2);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA), is(equalTo("editB"))); assertThat(
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB), is(equalTo(clusterBapiKey2))); remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA),
is(equalTo(ApiKeyService.withApiKeyPrefix("editB")))
);
assertThat(
remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB),
is(equalTo(ApiKeyService.withApiKeyPrefix(clusterBapiKey2)))
);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue())); assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue()));
// Remove clusterB authorization setting // Remove clusterB authorization setting
@ -106,7 +122,10 @@ public class RemoteClusterAuthorizationResolverTests extends ESTestCase {
final Settings newSettingsOmitClusterB = newSettingsOmitClusterBBuilder.build(); final Settings newSettingsOmitClusterB = newSettingsOmitClusterBBuilder.build();
final ClusterState newClusterState3 = createClusterState(clusterNameA, masterNodeA, newSettingsOmitClusterB); final ClusterState newClusterState3 = createClusterState(clusterNameA, masterNodeA, newSettingsOmitClusterB);
ClusterServiceUtils.setState(this.clusterService, newClusterState3); ClusterServiceUtils.setState(this.clusterService, newClusterState3);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA), is(equalTo("omitB"))); assertThat(
remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameA),
is(equalTo(ApiKeyService.withApiKeyPrefix("omitB")))
);
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB), is(nullValue())); assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterNameB), is(nullValue()));
assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue())); assertThat(remoteClusterAuthorizationResolver.resolveAuthorization(clusterDoesNotExist), is(nullValue()));
} }

View file

@ -58,6 +58,7 @@ import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser; import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
import org.elasticsearch.xpack.core.security.user.XPackUser; import org.elasticsearch.xpack.core.security.user.XPackUser;
import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.SSLService;
import org.elasticsearch.xpack.security.authc.ApiKeyService;
import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.RemoteAccessAuthenticationService; import org.elasticsearch.xpack.security.authc.RemoteAccessAuthenticationService;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
@ -81,8 +82,8 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN;
import static org.elasticsearch.xpack.core.ClientHelper.TRANSFORM_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.TRANSFORM_ORIGIN;
import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY; import static org.elasticsearch.xpack.core.security.authc.RemoteAccessAuthentication.REMOTE_ACCESS_AUTHENTICATION_HEADER_KEY;
import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomUniquelyNamedRoleDescriptors; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptorTests.randomUniquelyNamedRoleDescriptors;
import static org.elasticsearch.xpack.security.authc.RemoteAccessHeaders.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY;
import static org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor.REMOTE_ACCESS_ACTION_ALLOWLIST; import static org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor.REMOTE_ACCESS_ACTION_ALLOWLIST;
import static org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor.REMOTE_ACCESS_CLUSTER_CREDENTIAL_HEADER_KEY;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
@ -581,7 +582,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase {
: AuthenticationTestHelper.builder().user(new User(randomAlphaOfLengthBetween(3, 10), randomRoles())).realm().build(); : AuthenticationTestHelper.builder().user(new User(randomAlphaOfLengthBetween(3, 10), randomRoles())).realm().build();
authentication.writeToContext(threadContext); authentication.writeToContext(threadContext);
final RemoteClusterAuthorizationResolver remoteClusterAuthorizationResolver = mock(RemoteClusterAuthorizationResolver.class); final RemoteClusterAuthorizationResolver remoteClusterAuthorizationResolver = mock(RemoteClusterAuthorizationResolver.class);
final String remoteClusterCredential = randomAlphaOfLengthBetween(10, 42); final String remoteClusterCredential = ApiKeyService.withApiKeyPrefix(randomAlphaOfLengthBetween(10, 42));
when(remoteClusterAuthorizationResolver.resolveAuthorization(any())).thenReturn(remoteClusterCredential); when(remoteClusterAuthorizationResolver.resolveAuthorization(any())).thenReturn(remoteClusterCredential);
final String remoteClusterAlias = randomAlphaOfLengthBetween(5, 10); final String remoteClusterAlias = randomAlphaOfLengthBetween(5, 10);
final AuthorizationService authzService = mock(AuthorizationService.class); final AuthorizationService authzService = mock(AuthorizationService.class);
@ -664,7 +665,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase {
listenerCaptor.getValue().onResponse(expectedRoleDescriptorsIntersection); listenerCaptor.getValue().onResponse(expectedRoleDescriptorsIntersection);
} }
assertTrue(calledWrappedSender.get()); assertTrue(calledWrappedSender.get());
assertThat(sentCredential.get(), equalTo("ApiKey " + remoteClusterCredential)); assertThat(sentCredential.get(), equalTo(remoteClusterCredential));
assertThat( assertThat(
sentRemoteAccessAuthentication.get(), sentRemoteAccessAuthentication.get(),
equalTo(new RemoteAccessAuthentication(authentication, expectedRoleDescriptorsIntersection)) equalTo(new RemoteAccessAuthentication(authentication, expectedRoleDescriptorsIntersection))
@ -693,7 +694,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase {
} }
final RemoteClusterAuthorizationResolver remoteClusterAuthorizationResolver = mock(RemoteClusterAuthorizationResolver.class); final RemoteClusterAuthorizationResolver remoteClusterAuthorizationResolver = mock(RemoteClusterAuthorizationResolver.class);
when(remoteClusterAuthorizationResolver.resolveAuthorization(any())).thenReturn( when(remoteClusterAuthorizationResolver.resolveAuthorization(any())).thenReturn(
noCredential ? null : randomAlphaOfLengthBetween(10, 42) noCredential ? null : ApiKeyService.withApiKeyPrefix(randomAlphaOfLengthBetween(10, 42))
); );
final AuthenticationTestHelper.AuthenticationTestBuilder builder = AuthenticationTestHelper.builder(); final AuthenticationTestHelper.AuthenticationTestBuilder builder = AuthenticationTestHelper.builder();
final Authentication authentication; final Authentication authentication;
@ -773,7 +774,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase {
.build(); .build();
authentication.writeToContext(threadContext); authentication.writeToContext(threadContext);
final RemoteClusterAuthorizationResolver remoteClusterAuthorizationResolver = mock(RemoteClusterAuthorizationResolver.class); final RemoteClusterAuthorizationResolver remoteClusterAuthorizationResolver = mock(RemoteClusterAuthorizationResolver.class);
final String remoteClusterCredential = randomAlphaOfLengthBetween(10, 42); final String remoteClusterCredential = ApiKeyService.withApiKeyPrefix(randomAlphaOfLengthBetween(10, 42));
when(remoteClusterAuthorizationResolver.resolveAuthorization(any())).thenReturn(remoteClusterCredential); when(remoteClusterAuthorizationResolver.resolveAuthorization(any())).thenReturn(remoteClusterCredential);
final String remoteClusterAlias = randomAlphaOfLengthBetween(5, 10); final String remoteClusterAlias = randomAlphaOfLengthBetween(5, 10);
@ -981,4 +982,5 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase {
private static Consumer<ThreadContext.StoredContext> anyConsumer() { private static Consumer<ThreadContext.StoredContext> anyConsumer() {
return any(Consumer.class); return any(Consumer.class);
} }
} }