Skip to content

Commit 53ef68f

Browse files
Introduce remote cluster security interceptor and authenticator (#134245)
This refactoring is meant to be purely structural, with no functional changes. It introduces two interfaces: - `RemoteClusterAuthenticationService` - extracted based on the existing `CrossClusterAccessAuthenticationService` - `RemoteClusterTransportInterceptor` - which aims to abstract and move all "cross-cluster access" logic from `SecurityServerTransportInterceptor` This is prerequisite for making remote cluster security logic pluggable (which I plan to add in a followup PR). Relates to ES-12801
1 parent d6c34c3 commit 53ef68f

13 files changed

+700
-490
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
import org.elasticsearch.xpack.core.security.transport.SecurityTransportExceptionHandler;
4848
import org.elasticsearch.xpack.core.ssl.SSLService;
4949
import org.elasticsearch.xpack.core.ssl.SslProfile;
50-
import org.elasticsearch.xpack.security.authc.CrossClusterAccessAuthenticationService;
50+
import org.elasticsearch.xpack.security.authc.RemoteClusterAuthenticationService;
5151

5252
import java.net.InetSocketAddress;
5353
import java.net.SocketAddress;
@@ -79,7 +79,7 @@ public class SecurityNetty4Transport extends Netty4Transport {
7979
private final boolean remoteClusterServerSslEnabled;
8080
private final SslProfile remoteClusterClientSslProfile;
8181
private final RemoteClusterClientBootstrapOptions remoteClusterClientBootstrapOptions;
82-
private final CrossClusterAccessAuthenticationService crossClusterAccessAuthenticationService;
82+
private final RemoteClusterAuthenticationService remoteClusterAuthenticationService;
8383

8484
public SecurityNetty4Transport(
8585
final Settings settings,
@@ -91,7 +91,7 @@ public SecurityNetty4Transport(
9191
final CircuitBreakerService circuitBreakerService,
9292
final SSLService sslService,
9393
final SharedGroupFactory sharedGroupFactory,
94-
final CrossClusterAccessAuthenticationService crossClusterAccessAuthenticationService
94+
final RemoteClusterAuthenticationService remoteClusterAuthenticationService
9595
) {
9696
super(
9797
settings,
@@ -103,7 +103,7 @@ public SecurityNetty4Transport(
103103
circuitBreakerService,
104104
sharedGroupFactory
105105
);
106-
this.crossClusterAccessAuthenticationService = crossClusterAccessAuthenticationService;
106+
this.remoteClusterAuthenticationService = remoteClusterAuthenticationService;
107107
this.exceptionHandler = new SecurityTransportExceptionHandler(logger, lifecycle, (c, e) -> super.onException(c, e));
108108
this.sslService = sslService;
109109
this.transportSslEnabled = XPackSettings.TRANSPORT_SSL_ENABLED.get(settings);
@@ -180,7 +180,7 @@ protected void headerReceived(Header header) {
180180
channel.config().setAutoRead(false);
181181
// this prevents thread-context changes to propagate beyond the validation, as netty worker threads are reused
182182
try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().newStoredContext()) {
183-
crossClusterAccessAuthenticationService.tryAuthenticate(
183+
remoteClusterAuthenticationService.authenticateHeaders(
184184
header.getRequestHeaders(),
185185
ActionListener.runAfter(ActionListener.wrap(aVoid -> {
186186
// authn is successful -> NOOP (the complete request will be subsequently authn & authz & audited)

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@
417417
import org.elasticsearch.xpack.security.support.ReloadableSecurityComponent;
418418
import org.elasticsearch.xpack.security.support.SecurityMigrations;
419419
import org.elasticsearch.xpack.security.support.SecuritySystemIndices;
420+
import org.elasticsearch.xpack.security.transport.CrossClusterAccessTransportInterceptor;
421+
import org.elasticsearch.xpack.security.transport.RemoteClusterTransportInterceptor;
420422
import org.elasticsearch.xpack.security.transport.SecurityHttpSettings;
421423
import org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor;
422424
import org.elasticsearch.xpack.security.transport.filter.IPFilter;
@@ -1164,17 +1166,24 @@ Collection<Object> createComponents(
11641166
DestructiveOperations destructiveOperations = new DestructiveOperations(settings, clusterService.getClusterSettings());
11651167
crossClusterAccessAuthcService.set(new CrossClusterAccessAuthenticationService(clusterService, apiKeyService, authcService.get()));
11661168
components.add(crossClusterAccessAuthcService.get());
1169+
1170+
RemoteClusterTransportInterceptor remoteClusterTransportInterceptor = new CrossClusterAccessTransportInterceptor(
1171+
settings,
1172+
threadPool,
1173+
authcService.get(),
1174+
authzService,
1175+
securityContext.get(),
1176+
crossClusterAccessAuthcService.get(),
1177+
getLicenseState()
1178+
);
11671179
securityInterceptor.set(
11681180
new SecurityServerTransportInterceptor(
11691181
settings,
11701182
threadPool,
1171-
authcService.get(),
1172-
authzService,
11731183
getSslService(),
11741184
securityContext.get(),
11751185
destructiveOperations,
1176-
crossClusterAccessAuthcService.get(),
1177-
getLicenseState()
1186+
remoteClusterTransportInterceptor
11781187
)
11791188
);
11801189

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/CrossClusterAccessAuthenticationService.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import static org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo.CROSS_CLUSTER_ACCESS_SUBJECT_INFO_HEADER_KEY;
3535
import static org.elasticsearch.xpack.security.authc.CrossClusterAccessHeaders.CROSS_CLUSTER_ACCESS_CREDENTIALS_HEADER_KEY;
3636

37-
public class CrossClusterAccessAuthenticationService {
37+
public class CrossClusterAccessAuthenticationService implements RemoteClusterAuthenticationService {
3838

3939
private static final Logger logger = LogManager.getLogger(CrossClusterAccessAuthenticationService.class);
4040

@@ -52,6 +52,7 @@ public CrossClusterAccessAuthenticationService(
5252
this.authenticationService = authenticationService;
5353
}
5454

55+
@Override
5556
public void authenticate(final String action, final TransportRequest request, final ActionListener<Authentication> listener) {
5657
final ThreadContext threadContext = clusterService.threadPool().getThreadContext();
5758
final CrossClusterAccessHeaders crossClusterAccessHeaders;
@@ -117,7 +118,8 @@ public void authenticate(final String action, final TransportRequest request, fi
117118
}
118119
}
119120

120-
public void tryAuthenticate(Map<String, String> headers, ActionListener<Void> listener) {
121+
@Override
122+
public void authenticateHeaders(Map<String, String> headers, ActionListener<Void> listener) {
121123
final ApiKeyService.ApiKeyCredentials credentials;
122124
try {
123125
credentials = extractApiKeyCredentialsFromHeaders(headers);
@@ -128,7 +130,8 @@ public void tryAuthenticate(Map<String, String> headers, ActionListener<Void> li
128130
tryAuthenticate(credentials, listener);
129131
}
130132

131-
public void tryAuthenticate(ApiKeyService.ApiKeyCredentials credentials, ActionListener<Void> listener) {
133+
// package-private for testing
134+
void tryAuthenticate(ApiKeyService.ApiKeyCredentials credentials, ActionListener<Void> listener) {
132135
Objects.requireNonNull(credentials);
133136
apiKeyService.tryAuthenticate(clusterService.threadPool().getThreadContext(), credentials, ActionListener.wrap(authResult -> {
134137
if (authResult.isAuthenticated()) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.security.authc;
9+
10+
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.transport.TransportRequest;
12+
import org.elasticsearch.xpack.core.security.authc.Authentication;
13+
14+
import java.util.Map;
15+
16+
/**
17+
* Service interface for authenticating remote cluster requests.
18+
*
19+
* <p>
20+
* This service handles authentication for cross-cluster requests.
21+
* It provides methods to authenticate both full transport requests
22+
* and credential headers only.
23+
*/
24+
public interface RemoteClusterAuthenticationService {
25+
26+
/**
27+
* Called to authenticates a remote cluster transport request.
28+
*
29+
* @param action the transport action being performed
30+
* @param request the transport request containing authentication headers
31+
* @param listener callback to receive the authenticated {@link Authentication}
32+
* object on success, or an exception on failure
33+
*/
34+
void authenticate(String action, TransportRequest request, ActionListener<Authentication> listener);
35+
36+
/**
37+
* Called early (after transport headers were received) to authenticate a remote cluster transport request.
38+
*
39+
* @param headers map of request headers containing authentication credentials
40+
* @param listener callback to receive {@code null} on successful authentication,
41+
* or an exception on authentication failure
42+
*/
43+
void authenticateHeaders(Map<String, String> headers, ActionListener<Void> listener);
44+
45+
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/PreAuthorizationUtils.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
import java.util.Arrays;
2525
import java.util.Map;
26-
import java.util.Optional;
2726
import java.util.Set;
2827

2928
public final class PreAuthorizationUtils {
@@ -118,17 +117,17 @@ private static boolean shouldPreAuthorizeChildActionOfParent(final String parent
118117
}
119118

120119
public static boolean shouldRemoveParentAuthorizationFromThreadContext(
121-
Optional<String> remoteClusterAlias,
122120
String childAction,
123-
SecurityContext securityContext
121+
SecurityContext securityContext,
122+
boolean isRemoteClusterRequest
124123
) {
125124
final ParentActionAuthorization parentAuthorization = securityContext.getParentAuthorization();
126125
if (parentAuthorization == null) {
127126
// Nothing to remove.
128127
return false;
129128
}
130129

131-
if (remoteClusterAlias.isPresent()) {
130+
if (isRemoteClusterRequest) {
132131
// We never want to send the parent authorization header to remote clusters.
133132
return true;
134133
}

0 commit comments

Comments
 (0)