mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-06-28 17:34:17 -04:00
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:
parent
ee56ea8c82
commit
7d88d13312
14 changed files with 328 additions and 91 deletions
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue