From c710726a9e15e51a3525a52e5c01f8d7891183c9 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Wed, 14 Oct 2020 13:41:23 +0300 Subject: [PATCH 01/26] Remove redundant IOException --- .../action/filter/SecurityActionFilter.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 3d255b7690e3c..84fa48e70afbb 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -89,19 +89,11 @@ public void app try { if (useSystemUser) { securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> { - try { - applyInternal(action, request, authenticatedListener); - } catch (IOException e) { - listener.onFailure(e); - } + applyInternal(action, request, authenticatedListener); }, Version.CURRENT); } else if (AuthorizationUtils.shouldSetUserBasedOnActionOrigin(threadContext)) { AuthorizationUtils.switchUserBasedOnActionOriginAndExecute(threadContext, securityContext, (original) -> { - try { - applyInternal(action, request, authenticatedListener); - } catch (IOException e) { - listener.onFailure(e); - } + applyInternal(action, request, authenticatedListener); }); } else { try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(true)) { @@ -131,7 +123,7 @@ public int order() { } private void applyInternal(String action, Request request, - ActionListener listener) throws IOException { + ActionListener listener) { if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) { IndicesRequest indicesRequest = (IndicesRequest) request; try { From 48d647eee633e193a6f2059dbee18188606039f4 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 15 Oct 2020 01:19:29 +0300 Subject: [PATCH 02/26] WIP --- .../ml/MlUpgradeModeActionFilterTests.java | 2 +- .../xpack/security/Security.java | 5 +- .../action/filter/SecurityActionFilter.java | 16 +++++-- .../xpack/security/audit/AuditTrail.java | 5 ++ .../security/audit/AuditTrailService.java | 13 +++++ .../audit/logfile/LoggingAuditTrail.java | 7 +++ .../SecurityServerTransportInterceptor.java | 47 +++++++++++++++++-- .../filter/SecurityActionFilterTests.java | 8 ++-- ...curityServerTransportInterceptorTests.java | 35 +++++++------- 9 files changed, 110 insertions(+), 28 deletions(-) diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlUpgradeModeActionFilterTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlUpgradeModeActionFilterTests.java index 6058906de284e..f3c7f3afc67b8 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlUpgradeModeActionFilterTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlUpgradeModeActionFilterTests.java @@ -97,7 +97,7 @@ public void testApply_ActionAllowedInUpgradeMode() { public void testOrder_UpgradeFilterIsExecutedAfterSecurityFilter() { MlUpgradeModeActionFilter upgradeModeFilter = new MlUpgradeModeActionFilter(clusterService); - SecurityActionFilter securityFilter = new SecurityActionFilter(null, null, null, mock(ThreadPool.class), null, null); + SecurityActionFilter securityFilter = new SecurityActionFilter(null, null, null, null, mock(ThreadPool.class), null, null); ActionFilter[] actionFiltersInOrderOfExecution = new ActionFilters(Sets.newHashSet(upgradeModeFilter, securityFilter)).filters(); assertThat(actionFiltersInOrderOfExecution, is(arrayContaining(securityFilter, upgradeModeFilter))); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 4474e401ecbaf..7db08de216b6c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -503,9 +503,10 @@ auditTrailService, failureHandler, threadPool, anonymousUser, getAuthorizationEn components.add(ipFilter.get()); DestructiveOperations destructiveOperations = new DestructiveOperations(settings, clusterService.getClusterSettings()); securityInterceptor.set(new SecurityServerTransportInterceptor(settings, threadPool, authcService.get(), - authzService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, clusterService)); + authzService, auditTrailService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, + clusterService)); - securityActionFilter.set(new SecurityActionFilter(authcService.get(), authzService, getLicenseState(), + securityActionFilter.set(new SecurityActionFilter(authcService.get(), authzService, auditTrailService, getLicenseState(), threadPool, securityContext.get(), destructiveOperations)); components.add(new SecurityUsageServices(realms, allRolesStore, nativeRoleMappingStore, ipFilter.get())); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 84fa48e70afbb..21a2189117fa1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -33,11 +33,12 @@ import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.security.action.SecurityActionMapper; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationUtils; -import java.io.IOException; import java.util.function.Predicate; public class SecurityActionFilter implements ActionFilter { @@ -48,6 +49,7 @@ public class SecurityActionFilter implements ActionFilter { private final AuthenticationService authcService; private final AuthorizationService authzService; + private final AuditTrailService auditTrailService; private final SecurityActionMapper actionMapper = new SecurityActionMapper(); private final XPackLicenseState licenseState; private final ThreadContext threadContext; @@ -55,10 +57,11 @@ public class SecurityActionFilter implements ActionFilter { private final DestructiveOperations destructiveOperations; public SecurityActionFilter(AuthenticationService authcService, AuthorizationService authzService, - XPackLicenseState licenseState, ThreadPool threadPool, + AuditTrailService auditTrailService, XPackLicenseState licenseState, ThreadPool threadPool, SecurityContext securityContext, DestructiveOperations destructiveOperations) { this.authcService = authcService; this.authzService = authzService; + this.auditTrailService = auditTrailService; this.licenseState = licenseState; this.threadContext = threadPool.getThreadContext(); this.securityContext = securityContext; @@ -83,8 +86,15 @@ public void app if (licenseState.isSecurityEnabled()) { final ActionListener contextPreservingListener = ContextPreservingActionListener.wrapPreservingContext(listener, threadContext); + final ActionListener postActionExecutionListener = ActionListener.delegateFailure(contextPreservingListener, + (ignore, response) -> { + String requestId = AuditUtil.extractRequestId(threadContext); + Authentication authentication = securityContext.getAuthentication(); + auditTrailService.get().actionResponse(requestId, authentication, action, request, response); + contextPreservingListener.onResponse(response); + }); ActionListener authenticatedListener = ActionListener.wrap( - (aVoid) -> chain.proceed(task, action, request, contextPreservingListener), contextPreservingListener::onFailure); + (aVoid) -> chain.proceed(task, action, request, postActionExecutionListener), postActionExecutionListener::onFailure); final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action); try { if (useSystemUser) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java index 432b80578c76f..1e9f2d444ae34 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java @@ -8,6 +8,7 @@ import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo; @@ -47,6 +48,10 @@ void accessGranted(String requestId, Authentication authentication, String actio void accessDenied(String requestId, Authentication authentication, String action, TransportRequest transportRequest, AuthorizationInfo authorizationInfo); + // this is the only audit method that is called *after* the action executed, when the response is available + void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, + TransportResponse transportResponse); + void tamperedRequest(String requestId, RestRequest request); void tamperedRequest(String requestId, String action, TransportRequest transportRequest); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java index 4449cfca8190e..759b607d7c282 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java @@ -10,6 +10,7 @@ import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo; @@ -92,6 +93,10 @@ public void accessGranted(String requestId, Authentication authentication, Strin public void accessDenied(String requestId, Authentication authentication, String action, TransportRequest transportRequest, AuthorizationInfo authorizationInfo) {} + @Override + public void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, + TransportResponse transportResponse) {} + @Override public void tamperedRequest(String requestId, RestRequest request) {} @@ -230,6 +235,14 @@ public void accessDenied(String requestId, Authentication authentication, String } } + @Override + public void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, + TransportResponse transportResponse) { + for (AuditTrail auditTrail : auditTrails) { + auditTrail.actionResponse(requestId, authentication, action, transportRequest, transportResponse); + } + } + @Override public void tamperedRequest(String requestId, RestRequest request) { for (AuditTrail auditTrail : auditTrails) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java index 210d503151015..d3353060b30a1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java @@ -34,6 +34,7 @@ import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo; @@ -552,6 +553,12 @@ public void accessDenied(String requestId, Authentication authentication, String } } + @Override + public void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, + TransportResponse transportResponse) { + // not implemented yet + } + @Override public void tamperedRequest(String requestId, RestRequest request) { if (events.contains(TAMPERED_REQUEST) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index c6fdc4338eb16..bbae89f4c97e0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -32,14 +32,18 @@ import org.elasticsearch.transport.TransportService.ContextRestoreResponseHandler; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.SecurityContext; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.transport.ProfileConfigurations; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationUtils; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -53,6 +57,7 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor private final AuthenticationService authcService; private final AuthorizationService authzService; + private final AuditTrailService auditTrailService; private final SSLService sslService; private final Map profileFilters; private final XPackLicenseState licenseState; @@ -66,6 +71,7 @@ public SecurityServerTransportInterceptor(Settings settings, ThreadPool threadPool, AuthenticationService authcService, AuthorizationService authzService, + AuditTrailService auditTrailService, XPackLicenseState licenseState, SSLService sslService, SecurityContext securityContext, @@ -75,6 +81,7 @@ public SecurityServerTransportInterceptor(Settings settings, this.threadPool = threadPool; this.authcService = authcService; this.authzService = authzService; + this.auditTrailService = auditTrailService; this.licenseState = licenseState; this.sslService = sslService; this.securityContext = securityContext; @@ -160,7 +167,7 @@ public TransportRequestHandler interceptHandler( boolean forceExecution, TransportRequestHandler actualHandler) { return new ProfileSecuredRequestHandler<>(logger, action, forceExecution, executor, actualHandler, profileFilters, - licenseState, threadPool); + auditTrailService, securityContext, licenseState, threadPool); } private Map initializeProfileFilters(DestructiveOperations destructiveOperations) { @@ -185,6 +192,8 @@ public static class ProfileSecuredRequestHandler imp private final String action; private final TransportRequestHandler handler; private final Map profileFilters; + private final AuditTrailService auditTrailService; + private final SecurityContext securityContext; private final XPackLicenseState licenseState; private final ThreadContext threadContext; private final String executorName; @@ -194,12 +203,15 @@ public static class ProfileSecuredRequestHandler imp ProfileSecuredRequestHandler(Logger logger, String action, boolean forceExecution, String executorName, TransportRequestHandler handler, Map profileFilters, + AuditTrailService auditTrailService, SecurityContext securityContext, XPackLicenseState licenseState, ThreadPool threadPool) { this.logger = logger; this.action = action; this.executorName = executorName; this.handler = handler; this.profileFilters = profileFilters; + this.auditTrailService = auditTrailService; + this.securityContext = securityContext; this.licenseState = licenseState; this.threadContext = threadPool.getThreadContext(); this.threadPool = threadPool; @@ -225,7 +237,36 @@ public void onFailure(Exception e) { @Override protected void doRun() throws Exception { - handler.messageReceived(request, channel, task); + handler.messageReceived(request, new TransportChannel() { + + @Override + public String getProfileName() { + return channel.getProfileName(); + } + + @Override + public String getChannelType() { + return channel.getChannelType(); + } + + @Override + public Version getVersion() { + return channel.getVersion(); + } + + @Override + public void sendResponse(TransportResponse response) throws IOException { + String requestId = AuditUtil.extractRequestId(threadContext); + Authentication authentication = securityContext.getAuthentication(); + auditTrailService.get().actionResponse(requestId, authentication, action, request, response); + channel.sendResponse(response); + } + + @Override + public void sendResponse(Exception exception) throws IOException { + channel.sendResponse(exception); + } + }, task); } }; } @@ -259,7 +300,7 @@ public void messageReceived(T request, TransportChannel channel, Task task) thro assert filter != null; final Thread executingThread = Thread.currentThread(); - CheckedConsumer consumer = (x) -> { + CheckedConsumer consumer = (aVoid) -> { final Executor executor; if (executingThread == Thread.currentThread()) { // only fork off if we get called on another thread this means we moved to diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index 8d341671be657..6bb8e7ecb90ab 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.Before; @@ -88,7 +89,8 @@ public void init() throws Exception { when(state.nodes()).thenReturn(nodes); SecurityContext securityContext = new SecurityContext(settings, threadContext); - filter = new SecurityActionFilter(authcService, authzService, licenseState, threadPool, securityContext, destructiveOperations); + filter = new SecurityActionFilter(authcService, authzService, mock(AuditTrailService.class), licenseState, threadPool, + securityContext, destructiveOperations); } public void testApply() throws Exception { @@ -102,7 +104,7 @@ public void testApply() throws Exception { mockAuthorize(); filter.apply(task, "_action", request, listener, chain); verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class)); + verify(chain).proceed(eq(task), eq("_action"), eq(request), any(ActionListener.class)); } public void testApplyRestoresThreadContext() throws Exception { @@ -122,7 +124,7 @@ public void testApplyRestoresThreadContext() throws Exception { assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); assertNull(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class)); + verify(chain).proceed(eq(task), eq("_action"), eq(request), any(ActionListener.class)); } public void testApplyAsSystemUser() throws Exception { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java index 9c832ab59bb5c..debf8b488b0fe 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.After; @@ -63,6 +64,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase { private Settings settings; private ThreadPool threadPool; private ThreadContext threadContext; + private AuditTrailService auditTrailService; private XPackLicenseState xPackLicenseState; private SecurityContext securityContext; private ClusterService clusterService; @@ -74,6 +76,7 @@ public void setUp() throws Exception { threadPool = new TestThreadPool(getTestName()); clusterService = ClusterServiceUtils.createClusterService(threadPool); threadContext = threadPool.getThreadContext(); + auditTrailService = mock(AuditTrailService.class); securityContext = spy(new SecurityContext(settings, threadPool.getThreadContext())); xPackLicenseState = mock(XPackLicenseState.class); when(xPackLicenseState.isSecurityEnabled()).thenReturn(true); @@ -87,8 +90,8 @@ public void stopThreadPool() throws Exception { public void testSendAsyncUserActionWhenUnlicensed() { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener when(xPackLicenseState.isSecurityEnabled()).thenReturn(false); @@ -115,8 +118,8 @@ public void sendRequest(Transport.Connection conne public void testSendAsyncInternalActionWhenUnlicensed() { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener when(xPackLicenseState.isSecurityEnabled()).thenReturn(false); @@ -144,8 +147,8 @@ public void sendRequest(Transport.Connection conne public void testSendAsyncWithStateNotRecovered() { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); final boolean authAllowed = randomBoolean(); when(xPackLicenseState.isSecurityEnabled()).thenReturn(authAllowed); @@ -183,8 +186,8 @@ public void testSendAsync() throws Exception { final Authentication authentication = new Authentication(user, new RealmRef("ldap", "foo", "node1"), null); authentication.writeToContext(threadContext); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -219,8 +222,8 @@ public void testSendAsyncSwitchToSystem() throws Exception { threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -250,8 +253,8 @@ public void sendRequest(Transport.Connection conne public void testSendWithoutUser() throws Exception { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService) { @Override void assertNoAuthentication(String action) { @@ -286,8 +289,8 @@ public void testSendToNewerVersionSetsCorrectVersion() throws Exception { threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -327,8 +330,8 @@ public void testSendToOlderVersionSetsCorrectVersion() throws Exception { threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), - securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener From 946912741446d0017d52b7392c87a1063a29e211 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 15 Oct 2020 15:30:30 +0300 Subject: [PATCH 03/26] SecurityActionFilterTests --- .../security/action/filter/SecurityActionFilterTests.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index 6bb8e7ecb90ab..b4faf469010eb 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -14,7 +14,6 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.open.OpenIndexAction; import org.elasticsearch.action.support.ActionFilterChain; -import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.ClusterState; @@ -207,7 +206,7 @@ public void testApplyDestructiveOperations() throws Exception { verifyNoMoreInteractions(authzService, chain); } else { verify(authzService).authorize(eq(authentication), eq(action), eq(request), any(ActionListener.class)); - verify(chain).proceed(eq(task), eq(action), eq(request), isA(ContextPreservingActionListener.class)); + verify(chain).proceed(eq(task), eq(action), eq(request), any(ActionListener.class)); } } From 2f5e54682ab7abfb8f8e4c47d9b1dd5f70ab8503 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 15 Oct 2020 15:40:46 +0300 Subject: [PATCH 04/26] Strict! --- .../security/action/filter/SecurityActionFilter.java | 9 +++++++++ .../transport/SecurityServerTransportInterceptor.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 21a2189117fa1..70ea6d68a852a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; @@ -89,7 +90,15 @@ public void app final ActionListener postActionExecutionListener = ActionListener.delegateFailure(contextPreservingListener, (ignore, response) -> { String requestId = AuditUtil.extractRequestId(threadContext); + if (requestId == null) { + contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is missing unexpectedly")); + return; + } Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + contextPreservingListener.onFailure(new ElasticsearchSecurityException("authn is missing unexpectedly")); + return; + } auditTrailService.get().actionResponse(requestId, authentication, action, request, response); contextPreservingListener.onResponse(response); }); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index bbae89f4c97e0..5d67baf4780b2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -7,6 +7,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.DestructiveOperations; @@ -257,7 +258,15 @@ public Version getVersion() { @Override public void sendResponse(TransportResponse response) throws IOException { String requestId = AuditUtil.extractRequestId(threadContext); + if (requestId == null) { + channel.sendResponse(new ElasticsearchSecurityException("requestId is missing unexpectedly")); + return; + } Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + channel.sendResponse(new ElasticsearchSecurityException("authn is missing unexpectedly")); + return; + } auditTrailService.get().actionResponse(requestId, authentication, action, request, response); channel.sendResponse(response); } From 0ebadb9c29daf47abdbd77a2427376037286607c Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 26 Oct 2020 20:25:54 +0200 Subject: [PATCH 05/26] Trash test --- .../action/filter/SecurityActionFilter.java | 21 +++++++++---------- .../security/authz/AuthorizationService.java | 21 +++++++++++-------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 70ea6d68a852a..5300a59888cce 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -8,7 +8,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; @@ -90,16 +89,16 @@ public void app final ActionListener postActionExecutionListener = ActionListener.delegateFailure(contextPreservingListener, (ignore, response) -> { String requestId = AuditUtil.extractRequestId(threadContext); - if (requestId == null) { - contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is missing unexpectedly")); - return; - } - Authentication authentication = securityContext.getAuthentication(); - if (authentication == null) { - contextPreservingListener.onFailure(new ElasticsearchSecurityException("authn is missing unexpectedly")); - return; - } - auditTrailService.get().actionResponse(requestId, authentication, action, request, response); +// if (requestId == null) { +// contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is missing unexpectedly")); +// return; +// } +// Authentication authentication = securityContext.getAuthentication(); +// if (authentication == null) { +// contextPreservingListener.onFailure(new ElasticsearchSecurityException("authn is missing unexpectedly")); +// return; +// } +// auditTrailService.get().actionResponse(requestId, authentication, action, request, response); contextPreservingListener.onResponse(response); }); ActionListener authenticatedListener = ActionListener.wrap( diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index 6d7d4d896c009..4d7ee232783fe 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -187,15 +187,18 @@ public void authorize(final Authentication authentication, final String action, if (auditId == null) { // We would like to assert that there is an existing request-id, but if this is a system action, then that might not be // true because the request-id is generated during authentication - if (isInternalUser(authentication.getUser()) != false) { - auditId = AuditUtil.getOrGenerateRequestId(threadContext); - } else { - auditTrailService.get().tamperedRequest(null, authentication, action, originalRequest); - final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal() - + "] without an existing request-id"; - assert false : message; - listener.onFailure(new ElasticsearchSecurityException(message)); - } + listener.onFailure(new ElasticsearchSecurityException("OOOPS")); + return; +// if (isInternalUser(authentication.getUser())) { +// auditId = AuditUtil.getOrGenerateRequestId(threadContext); +// } else { +// auditTrailService.get().tamperedRequest(null, authentication, action, originalRequest); +// final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal() +// + "] without an existing request-id"; +// assert false : message; +// listener.onFailure(new ElasticsearchSecurityException(message)); +// return; +// } } // sometimes a request might be wrapped within another, which is the case for proxied From f6ff433397a48346da425cd55fa9d08f656aea87 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 26 Oct 2020 23:26:35 +0200 Subject: [PATCH 06/26] SecurityActionFilter --- .../action/filter/SecurityActionFilter.java | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 5300a59888cce..e60e1e1e1e2c8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; @@ -86,23 +87,24 @@ public void app if (licenseState.isSecurityEnabled()) { final ActionListener contextPreservingListener = ContextPreservingActionListener.wrapPreservingContext(listener, threadContext); - final ActionListener postActionExecutionListener = ActionListener.delegateFailure(contextPreservingListener, - (ignore, response) -> { - String requestId = AuditUtil.extractRequestId(threadContext); -// if (requestId == null) { -// contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is missing unexpectedly")); -// return; -// } -// Authentication authentication = securityContext.getAuthentication(); -// if (authentication == null) { -// contextPreservingListener.onFailure(new ElasticsearchSecurityException("authn is missing unexpectedly")); -// return; -// } -// auditTrailService.get().actionResponse(requestId, authentication, action, request, response); - contextPreservingListener.onResponse(response); + final ActionListener authenticatedListener = ActionListener.delegateFailure(contextPreservingListener, + (ignore, aVoid) -> { + final String requestId = AuditUtil.extractRequestId(threadContext); + if (requestId == null) { + contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is unexpectedly missing")); + return; + } + final Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + contextPreservingListener.onFailure(new ElasticsearchSecurityException("authn is unexpectedly missing")); + return; + } + chain.proceed(task, action, request, ActionListener.delegateFailure(contextPreservingListener, + (ignore2, response) -> { + auditTrailService.get().actionResponse(requestId, authentication, action, request, response); + contextPreservingListener.onResponse(response); + })); }); - ActionListener authenticatedListener = ActionListener.wrap( - (aVoid) -> chain.proceed(task, action, request, postActionExecutionListener), postActionExecutionListener::onFailure); final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action); try { if (useSystemUser) { From 9f5623880cb6eb786b70949822e67182209b6ca9 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 27 Oct 2020 00:48:53 +0200 Subject: [PATCH 07/26] extract requestId and authentication before running the action --- .../action/filter/SecurityActionFilter.java | 1 + .../SecurityServerTransportInterceptor.java | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index e60e1e1e1e2c8..ff48c52ae0899 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -89,6 +89,7 @@ public void app ContextPreservingActionListener.wrapPreservingContext(listener, threadContext); final ActionListener authenticatedListener = ActionListener.delegateFailure(contextPreservingListener, (ignore, aVoid) -> { + // extract the requestId and the authentication from the threadContext before executing the action final String requestId = AuditUtil.extractRequestId(threadContext); if (requestId == null) { contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is unexpectedly missing")); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index 5d67baf4780b2..00d40be4f18e5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -238,6 +238,17 @@ public void onFailure(Exception e) { @Override protected void doRun() throws Exception { + // extract the requestId and the authentication from the threadContext before executing the action + final String requestId = AuditUtil.extractRequestId(threadContext); + if (requestId == null) { + channel.sendResponse(new ElasticsearchSecurityException("requestId is unexpectedly missing")); + return; + } + final Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + channel.sendResponse(new ElasticsearchSecurityException("authn is unexpectedly missing")); + return; + } handler.messageReceived(request, new TransportChannel() { @Override @@ -257,16 +268,6 @@ public Version getVersion() { @Override public void sendResponse(TransportResponse response) throws IOException { - String requestId = AuditUtil.extractRequestId(threadContext); - if (requestId == null) { - channel.sendResponse(new ElasticsearchSecurityException("requestId is missing unexpectedly")); - return; - } - Authentication authentication = securityContext.getAuthentication(); - if (authentication == null) { - channel.sendResponse(new ElasticsearchSecurityException("authn is missing unexpectedly")); - return; - } auditTrailService.get().actionResponse(requestId, authentication, action, request, response); channel.sendResponse(response); } From 765493f319444bb477fd9d089361a3407524656a Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 27 Oct 2020 00:52:04 +0200 Subject: [PATCH 08/26] remove thrash --- .../security/authz/AuthorizationService.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index 4d7ee232783fe..2eb8464444b51 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -187,18 +187,16 @@ public void authorize(final Authentication authentication, final String action, if (auditId == null) { // We would like to assert that there is an existing request-id, but if this is a system action, then that might not be // true because the request-id is generated during authentication - listener.onFailure(new ElasticsearchSecurityException("OOOPS")); - return; -// if (isInternalUser(authentication.getUser())) { -// auditId = AuditUtil.getOrGenerateRequestId(threadContext); -// } else { -// auditTrailService.get().tamperedRequest(null, authentication, action, originalRequest); -// final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal() -// + "] without an existing request-id"; -// assert false : message; -// listener.onFailure(new ElasticsearchSecurityException(message)); -// return; -// } + if (isInternalUser(authentication.getUser())) { + auditId = AuditUtil.getOrGenerateRequestId(threadContext); + } else { + auditTrailService.get().tamperedRequest(null, authentication, action, originalRequest); + final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal() + + "] without an existing request-id"; + assert false : message; + listener.onFailure(new ElasticsearchSecurityException(message)); + return; + } } // sometimes a request might be wrapped within another, which is the case for proxied From 71790f9d14cd74bc461c4fa46b822541f7d37478 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 27 Oct 2020 01:11:45 +0200 Subject: [PATCH 09/26] Security enabled guard in SecurityServerTransport --- .../transport/SecurityServerTransportInterceptor.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index 00d40be4f18e5..0b140b9af1cc4 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -240,12 +240,13 @@ public void onFailure(Exception e) { protected void doRun() throws Exception { // extract the requestId and the authentication from the threadContext before executing the action final String requestId = AuditUtil.extractRequestId(threadContext); - if (requestId == null) { + final boolean securityEnabled = licenseState.isSecurityEnabled(); + if (securityEnabled && requestId == null) { channel.sendResponse(new ElasticsearchSecurityException("requestId is unexpectedly missing")); return; } final Authentication authentication = securityContext.getAuthentication(); - if (authentication == null) { + if (securityEnabled && authentication == null) { channel.sendResponse(new ElasticsearchSecurityException("authn is unexpectedly missing")); return; } @@ -268,7 +269,9 @@ public Version getVersion() { @Override public void sendResponse(TransportResponse response) throws IOException { - auditTrailService.get().actionResponse(requestId, authentication, action, request, response); + if (securityEnabled) { + auditTrailService.get().actionResponse(requestId, authentication, action, request, response); + } channel.sendResponse(response); } From e25903e8670d157e6bfb7fd979a5f8e703d8e9d9 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 27 Oct 2020 18:22:41 +0200 Subject: [PATCH 10/26] AuthenticationServiceTests --- .../action/filter/SecurityActionFilter.java | 15 +- .../authc/AuthenticationServiceTests.java | 557 ++++++++++++++---- 2 files changed, 464 insertions(+), 108 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index ff48c52ae0899..580bc2c397268 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -30,6 +30,7 @@ import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.SystemUser; @@ -87,7 +88,7 @@ public void app if (licenseState.isSecurityEnabled()) { final ActionListener contextPreservingListener = ContextPreservingActionListener.wrapPreservingContext(listener, threadContext); - final ActionListener authenticatedListener = ActionListener.delegateFailure(contextPreservingListener, + final ActionListener postAuthzListener = ActionListener.delegateFailure(contextPreservingListener, (ignore, aVoid) -> { // extract the requestId and the authentication from the threadContext before executing the action final String requestId = AuditUtil.extractRequestId(threadContext); @@ -110,15 +111,15 @@ public void app try { if (useSystemUser) { securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> { - applyInternal(action, request, authenticatedListener); + applyInternal(action, request, postAuthzListener); }, Version.CURRENT); } else if (AuthorizationUtils.shouldSetUserBasedOnActionOrigin(threadContext)) { AuthorizationUtils.switchUserBasedOnActionOriginAndExecute(threadContext, securityContext, (original) -> { - applyInternal(action, request, authenticatedListener); + applyInternal(action, request, postAuthzListener); }); } else { try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(true)) { - applyInternal(action, request, authenticatedListener); + applyInternal(action, request, postAuthzListener); } } } catch (Exception e) { @@ -143,8 +144,7 @@ public int order() { return Integer.MIN_VALUE; } - private void applyInternal(String action, Request request, - ActionListener listener) { + private void applyInternal(String action, Request request, ActionListener listener) { if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) { IndicesRequest indicesRequest = (IndicesRequest) request; try { @@ -183,8 +183,7 @@ private void authorizeRequest(Authentication aut if (authentication == null) { listener.onFailure(new IllegalArgumentException("authentication must be non null for authorization")); } else { - authzService.authorize(authentication, securityAction, request, ActionListener.wrap(ignore -> listener.onResponse(null), - listener::onFailure)); + authzService.authorize(authentication, securityAction, request, listener); } } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index badfd859e80d1..ab71a81497ab1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -33,6 +33,7 @@ import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; @@ -73,6 +74,7 @@ import org.elasticsearch.xpack.core.security.authc.DefaultAuthenticationFailureHandler; import org.elasticsearch.xpack.core.security.authc.Realm; import org.elasticsearch.xpack.core.security.authc.Realm.Factory; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo; @@ -297,17 +299,26 @@ public void testTokenMissing() throws Exception { Mockito.doReturn(List.of(secondRealm)).when(realms).getUnlicensedRealms(); Mockito.doReturn(List.of(firstRealm)).when(realms).asList(); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } PlainActionFuture future = new PlainActionFuture<>(); Authenticator authenticator = service.createAuthenticator("_action", transportRequest, true, future); authenticator.extractToken((token) -> { assertThat(token, nullValue()); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } authenticator.handleNullToken(); }); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> future.actionGet()); assertThat(e.getMessage(), containsString("missing authentication credentials")); - verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest); + verify(auditTrail).anonymousAccessDenied(reqId.get(), "_action", transportRequest); verifyNoMoreInteractions(auditTrail); mockAppender.assertAllExpectationsMatched(); } finally { @@ -327,10 +338,20 @@ public void testAuthenticateBothSupportSecondSucceeds() throws Exception { } else { when(secondRealm.token(threadContext)).thenReturn(token); } - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + assertThat(reqId.get(), not(nullValue())); + } final AtomicBoolean completed = new AtomicBoolean(false); service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getLookedUpBy(), is(nullValue())); @@ -340,7 +361,7 @@ public void testAuthenticateBothSupportSecondSucceeds() throws Exception { setCompletedToTrue(completed); }, this::logAndFail)); assertTrue(completed.get()); - verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", transportRequest); + verify(auditTrail).authenticationFailed(reqId.get(), firstRealm.name(), token, "_action", transportRequest); verify(realms).asList(); verifyNoMoreInteractions(realms); } @@ -352,11 +373,20 @@ public void testAuthenticateSmartRealmOrdering() { when(secondRealm.supports(token)).thenReturn(true); mockAuthenticate(secondRealm, token, user); when(secondRealm.token(threadContext)).thenReturn(token); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } // Authenticate against the normal chain. 1st Realm will be checked (and not pass) then 2nd realm will successfully authc final AtomicBoolean completed = new AtomicBoolean(false); service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getLookedUpBy(), is(nullValue())); @@ -364,7 +394,7 @@ public void testAuthenticateSmartRealmOrdering() { assertThat(result.getAuthenticatedBy().getName(), is(SECOND_REALM_NAME)); assertThat(result.getAuthenticatedBy().getType(), is(SECOND_REALM_TYPE)); assertThreadContextContainsAuthentication(result); - verify(auditTrail).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); assertTrue(completed.get()); @@ -374,6 +404,7 @@ public void testAuthenticateSmartRealmOrdering() { // "SecondRealm" will be at the top of the list and will successfully authc. // "FirstRealm" will not be used service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getLookedUpBy(), is(nullValue())); @@ -381,11 +412,11 @@ public void testAuthenticateSmartRealmOrdering() { assertThat(result.getAuthenticatedBy().getName(), is(SECOND_REALM_NAME)); assertThat(result.getAuthenticatedBy().getType(), is(SECOND_REALM_TYPE)); assertThreadContextContainsAuthentication(result); - verify(auditTrail, times(2)).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail, times(2)).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); - verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", transportRequest); + verify(auditTrail).authenticationFailed(reqId.get(), firstRealm.name(), token, "_action", transportRequest); verify(firstRealm, times(2)).name(); // used above one time verify(secondRealm, times(2)).name(); verify(secondRealm, times(2)).type(); // used to create realm ref @@ -406,6 +437,7 @@ public void testAuthenticateSmartRealmOrdering() { // "SecondRealm" will be at the top of the list but will no longer authenticate the user. // Then "FirstRealm" will be checked. service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getLookedUpBy(), is(nullValue())); @@ -413,11 +445,11 @@ public void testAuthenticateSmartRealmOrdering() { assertThat(result.getAuthenticatedBy().getName(), is(FIRST_REALM_NAME)); assertThat(result.getAuthenticatedBy().getType(), is(FIRST_REALM_TYPE)); assertThreadContextContainsAuthentication(result); - verify(auditTrail).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); - verify(auditTrail).authenticationFailed(reqId, SECOND_REALM_NAME, token, "_action", transportRequest); + verify(auditTrail).authenticationFailed(reqId.get(), SECOND_REALM_NAME, token, "_action", transportRequest); verify(secondRealm, times(3)).authenticate(eq(token), any(ActionListener.class)); // 2 from above + 1 more verify(firstRealm, times(2)).authenticate(eq(token), any(ActionListener.class)); // 1 from above + 1 more } @@ -471,31 +503,41 @@ public void testAuthenticateSmartRealmOrderingDisabled() { when(secondRealm.supports(token)).thenReturn(true); mockAuthenticate(secondRealm, token, user); when(secondRealm.token(threadContext)).thenReturn(token); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } final AtomicBoolean completed = new AtomicBoolean(false); service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getLookedUpBy(), is(nullValue())); assertThat(result.getAuthenticatedBy().getName(), is(SECOND_REALM_NAME)); // TODO implement equals assertThreadContextContainsAuthentication(result); - verify(auditTrail).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); assertTrue(completed.get()); completed.set(false); service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getLookedUpBy(), is(nullValue())); assertThat(result.getAuthenticatedBy().getName(), is(SECOND_REALM_NAME)); // TODO implement equals assertThreadContextContainsAuthentication(result); - verify(auditTrail, times(2)).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail, times(2)).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); - verify(auditTrail, times(2)).authenticationFailed(reqId, firstRealm.name(), token, "_action", transportRequest); + verify(auditTrail, times(2)).authenticationFailed(reqId.get(), firstRealm.name(), token, "_action", transportRequest); verify(firstRealm, times(3)).name(); // used above one time verify(secondRealm, times(2)).name(); verify(secondRealm, times(2)).type(); // used to create realm ref @@ -514,17 +556,26 @@ public void testAuthenticateFirstNotSupportingSecondSucceeds() throws Exception when(secondRealm.supports(token)).thenReturn(true); mockAuthenticate(secondRealm, token, user); when(secondRealm.token(threadContext)).thenReturn(token); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } final AtomicBoolean completed = new AtomicBoolean(false); service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); assertThat(result.getAuthenticatedBy().getName(), is(secondRealm.name())); // TODO implement equals assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); assertThreadContextContainsAuthentication(result); - verify(auditTrail).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); verifyNoMoreInteractions(auditTrail); @@ -535,12 +586,21 @@ public void testAuthenticateFirstNotSupportingSecondSucceeds() throws Exception public void testAuthenticateCached() throws Exception { final Authentication authentication = new Authentication(new User("_username", "r1"), new RealmRef("test", "cached", "foo"), null); authentication.writeToContext(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } - Authentication result = authenticateBlocking("_action", transportRequest, null); + Tuple result = authenticateBlocking("_action", transportRequest, null); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } + assertThat(expectAuditRequestId(threadContext), is(result.v2())); assertThat(result, notNullValue()); - assertThat(result, is(authentication)); - assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); + assertThat(result.v1(), is(authentication)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); verifyZeroInteractions(auditTrail); verifyZeroInteractions(firstRealm); verifyZeroInteractions(secondRealm); @@ -553,6 +613,7 @@ public void testAuthenticateNonExistentRestRequestUserThrowsAuthenticationExcept authenticateBlocking(restRequest); fail("Authentication was successful but should not"); } catch (ElasticsearchSecurityException e) { + expectAuditRequestId(threadContext); assertAuthenticationException(e, containsString("unable to authenticate user [idonotexist] for REST request [/]")); } } @@ -563,6 +624,7 @@ public void testTokenRestMissing() throws Exception { Authenticator authenticator = service.createAuthenticator(restRequest, true, mock(ActionListener.class)); authenticator.extractToken((token) -> { + expectAuditRequestId(threadContext); assertThat(token, nullValue()); }); } @@ -572,12 +634,21 @@ public void authenticationInContextAndHeader() throws Exception { when(firstRealm.token(threadContext)).thenReturn(token); when(firstRealm.supports(token)).thenReturn(true); mockAuthenticate(firstRealm, token, user); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } - Authentication result = authenticateBlocking("_action", transportRequest, null); + Tuple result = authenticateBlocking("_action", transportRequest, null); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } + assertThat(expectAuditRequestId(threadContext), is(result.v2())); assertThat(result, notNullValue()); - assertThat(result.getUser(), is(user)); - assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); + assertThat(result.v1().getUser(), is(user)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); String userStr = threadContext.getHeader(AuthenticationField.AUTHENTICATION_KEY); assertThat(userStr, notNullValue()); @@ -590,7 +661,11 @@ public void authenticationInContextAndHeader() throws Exception { public void testAuthenticateTransportAnonymous() throws Exception { when(firstRealm.token(threadContext)).thenReturn(null); when(secondRealm.token(threadContext)).thenReturn(null); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } try { authenticateBlocking("_action", transportRequest, null); fail("expected an authentication exception when trying to authenticate an anonymous message"); @@ -598,7 +673,12 @@ public void testAuthenticateTransportAnonymous() throws Exception { // expected assertAuthenticationException(e); } - verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).anonymousAccessDenied(reqId.get(), "_action", transportRequest); } public void testAuthenticateRestAnonymous() throws Exception { @@ -611,24 +691,38 @@ public void testAuthenticateRestAnonymous() throws Exception { // expected assertAuthenticationException(e); } - String reqId = expectAuditRequestId(); - verify(auditTrail).anonymousAccessDenied(reqId, restRequest); + verify(auditTrail).anonymousAccessDenied(expectAuditRequestId(threadContext), restRequest); } public void testAuthenticateTransportFallback() throws Exception { when(firstRealm.token(threadContext)).thenReturn(null); when(secondRealm.token(threadContext)).thenReturn(null); User user1 = new User("username", "r1", "r2"); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } - Authentication result = authenticateBlocking("_action", transportRequest, user1); + Tuple result = authenticateBlocking("_action", transportRequest, user1); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + assertThat(expectAuditRequestId(threadContext), is(result.v2())); assertThat(result, notNullValue()); - assertThat(result.getUser(), sameInstance(user1)); - assertThat(result.getAuthenticationType(), is(AuthenticationType.INTERNAL)); - assertThreadContextContainsAuthentication(result); + assertThat(result.v1().getUser(), sameInstance(user1)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.INTERNAL)); + assertThreadContextContainsAuthentication(result.v1()); } public void testAuthenticateTransportDisabledUser() throws Exception { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } User user = new User("username", new String[] { "r1", "r2" }, null, null, Map.of(), false); User fallback = randomBoolean() ? SystemUser.INSTANCE : null; when(firstRealm.token(threadContext)).thenReturn(token); @@ -637,7 +731,12 @@ public void testAuthenticateTransportDisabledUser() throws Exception { ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, fallback)); - verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), token, "_action", transportRequest); verifyNoMoreInteractions(auditTrail); assertAuthenticationException(e); } @@ -650,14 +749,18 @@ public void testAuthenticateRestDisabledUser() throws Exception { ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking(restRequest)); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationFailed(reqId, token, restRequest); verifyNoMoreInteractions(auditTrail); assertAuthenticationException(e); } public void testAuthenticateTransportSuccess() throws Exception { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } final User user = new User("username", "r1", "r2"); final Consumer> authenticate; if (randomBoolean()) { @@ -671,12 +774,17 @@ public void testAuthenticateTransportSuccess() throws Exception { final AtomicBoolean completed = new AtomicBoolean(false); authenticate.accept(ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); assertThat(result.getUser(), sameInstance(user)); assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); assertThat(result.getAuthenticatedBy().getName(), is(firstRealm.name())); // TODO implement equals assertThreadContextContainsAuthentication(result); - verify(auditTrail).authenticationSuccess(reqId, result, "_action", transportRequest); + verify(auditTrail).authenticationSuccess(reqId.get(), result, "_action", transportRequest); setCompletedToTrue(completed); }, this::logAndFail)); @@ -697,7 +805,7 @@ public void testAuthenticateRestSuccess() throws Exception { assertThat(authentication.getAuthenticationType(), is(AuthenticationType.REALM)); assertThat(authentication.getAuthenticatedBy().getName(), is(firstRealm.name())); // TODO implement equals assertThreadContextContainsAuthentication(authentication); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationSuccess(reqId, authentication, restRequest); setCompletedToTrue(completed); }, this::logAndFail)); @@ -714,7 +822,17 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { final SetOnce authRef = new SetOnce<>(); final SetOnce authHeaderRef = new SetOnce<>(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } service.authenticate("_action", transportRequest, SystemUser.INSTANCE, ActionListener.wrap(authentication -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(authentication, notNullValue()); assertThat(authentication.getUser(), sameInstance(user1)); assertThat(authentication.getAuthenticationType(), is(AuthenticationType.REALM)); @@ -735,10 +853,20 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { service = new AuthenticationService(Settings.EMPTY, realms, auditTrailService, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool1, new AnonymousUser(Settings.EMPTY), tokenService, apiKeyService); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext1)); + } threadContext1.putTransient(AuthenticationField.AUTHENTICATION_KEY, authRef.get()); threadContext1.putHeader(AuthenticationField.AUTHENTICATION_KEY, authHeaderRef.get()); service.authenticate("_action", message1, SystemUser.INSTANCE, ActionListener.wrap(ctxAuth -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext1), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext1)); + } assertThat(ctxAuth, sameInstance(authRef.get())); assertThat(threadContext1.getHeader(AuthenticationField.AUTHENTICATION_KEY), sameInstance(authHeaderRef.get())); setCompletedToTrue(completed); @@ -754,6 +882,11 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { ThreadPool threadPool2 = new TestThreadPool("testAutheticateTransportContextAndHeader2"); try { ThreadContext threadContext2 = threadPool2.getThreadContext(); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext2)); + } final String header; try (ThreadContext.StoredContext ignore = threadContext2.stashContext()) { service = new AuthenticationService(Settings.EMPTY, realms, auditTrailService, @@ -774,6 +907,11 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool2, new AnonymousUser(Settings.EMPTY), tokenService, apiKeyService); service.authenticate("_action", new InternalRequest(), SystemUser.INSTANCE, ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadPool2.getThreadContext()), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadPool2.getThreadContext())); + } assertThat(result, notNullValue()); assertThat(result.getUser(), equalTo(user1)); assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); @@ -789,13 +927,21 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { public void testAuthenticateTamperedUser() throws Exception { InternalRequest message = new InternalRequest(); threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, "_signed_auth"); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); - + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } try { authenticateBlocking("_action", message, randomBoolean() ? SystemUser.INSTANCE : null); } catch (Exception e) { //expected - verify(auditTrail).tamperedRequest(reqId, "_action", message); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).tamperedRequest(reqId.get(), "_action", message); verifyNoMoreInteractions(auditTrail); } } @@ -813,11 +959,20 @@ public void testWrongTokenDoesNotFallbackToAnonymous() { new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, tokenService, apiKeyService); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } threadContext.putHeader("Authorization", "Bearer thisisaninvalidtoken"); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null)); - verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).anonymousAccessDenied(reqId.get(), "_action", transportRequest); verifyNoMoreInteractions(auditTrail); assertAuthenticationException(e); } @@ -843,11 +998,20 @@ public void testWrongApiKeyDoesNotFallbackToAnonymous() { return Void.TYPE; }).when(client).get(any(GetRequest.class), any(ActionListener.class)); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } threadContext.putHeader("Authorization", "ApiKey dGhpc2lzYW5pbnZhbGlkaWQ6dGhpc2lzYW5pbnZhbGlkc2VjcmV0"); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null)); - verify(auditTrail).anonymousAccessDenied(reqId, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).anonymousAccessDenied(reqId.get(), "_action", transportRequest); verifyNoMoreInteractions(auditTrail); assertAuthenticationException(e); } @@ -867,14 +1031,14 @@ public void testAnonymousUserRest() throws Exception { threadPool, anonymousUser, tokenService, apiKeyService); RestRequest request = new FakeRestRequest(); - Authentication result = authenticateBlocking(request); + Tuple result = authenticateBlocking(request); assertThat(result, notNullValue()); - assertThat(result.getUser(), sameInstance((Object) anonymousUser)); - assertThat(result.getAuthenticationType(), is(AuthenticationType.ANONYMOUS)); - assertThreadContextContainsAuthentication(result); - String reqId = expectAuditRequestId(); - verify(auditTrail).authenticationSuccess(reqId, result, request); + assertThat(result.v1().getUser(), sameInstance((Object) anonymousUser)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.ANONYMOUS)); + assertThreadContextContainsAuthentication(result.v1()); + assertThat(expectAuditRequestId(threadContext), is(result.v2())); + verify(auditTrail).authenticationSuccess(result.v2(), result.v1(), request); verifyNoMoreInteractions(auditTrail); } @@ -901,7 +1065,7 @@ public void testAuthenticateRestRequestDisallowAnonymous() throws Exception { assertThat(ex, throwableWithMessage(containsString("missing authentication credentials for REST request"))); assertThat(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY), nullValue()); assertThat(threadContext.getHeader(AuthenticationField.AUTHENTICATION_KEY), nullValue()); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).anonymousAccessDenied(reqId, request); verifyNoMoreInteractions(auditTrail); } @@ -915,12 +1079,23 @@ public void testAnonymousUserTransportNoDefaultUser() throws Exception { new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, tokenService, apiKeyService); InternalRequest message = new InternalRequest(); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } - Authentication result = authenticateBlocking("_action", message, null); + Tuple result = authenticateBlocking("_action", message, null); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); - assertThat(result.getUser(), sameInstance(anonymousUser)); - assertThat(result.getAuthenticationType(), is(AuthenticationType.ANONYMOUS)); - assertThreadContextContainsAuthentication(result); + assertThat(expectAuditRequestId(threadContext), is(result.v2())); + assertThat(result.v1().getUser(), sameInstance(anonymousUser)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.ANONYMOUS)); + assertThreadContextContainsAuthentication(result.v1()); } public void testAnonymousUserTransportWithDefaultUser() throws Exception { @@ -931,25 +1106,44 @@ public void testAnonymousUserTransportWithDefaultUser() throws Exception { service = new AuthenticationService(settings, realms, auditTrailService, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, tokenService, apiKeyService); - InternalRequest message = new InternalRequest(); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } - Authentication result = authenticateBlocking("_action", message, SystemUser.INSTANCE); + Tuple result = authenticateBlocking("_action", message, SystemUser.INSTANCE); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } assertThat(result, notNullValue()); - assertThat(result.getUser(), sameInstance(SystemUser.INSTANCE)); - assertThat(result.getAuthenticationType(), is(AuthenticationType.INTERNAL)); - assertThreadContextContainsAuthentication(result); + assertThat(expectAuditRequestId(threadContext), is(result.v2())); + assertThat(result.v1().getUser(), sameInstance(SystemUser.INSTANCE)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.INTERNAL)); + assertThreadContextContainsAuthentication(result.v1()); } public void testRealmTokenThrowingException() throws Exception { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } when(firstRealm.token(threadContext)).thenThrow(authenticationError("realm doesn't like tokens")); try { authenticateBlocking("_action", transportRequest, null); fail("exception should bubble out"); } catch (ElasticsearchException e) { assertThat(e.getMessage(), is("realm doesn't like tokens")); - verify(auditTrail).authenticationFailed(reqId, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), "_action", transportRequest); } } @@ -960,7 +1154,7 @@ public void testRealmTokenThrowingExceptionRest() throws Exception { fail("exception should bubble out"); } catch (ElasticsearchException e) { assertThat(e.getMessage(), is("realm doesn't like tokens")); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationFailed(reqId, restRequest); } } @@ -990,13 +1184,17 @@ public void testRealmSupportsMethodThrowingExceptionRest() throws Exception { fail("exception should bubble out"); } catch (ElasticsearchException e) { assertThat(e.getMessage(), is("realm doesn't like supports")); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationFailed(reqId, token, restRequest); } } public void testRealmAuthenticateTerminateAuthenticationProcessWithException() { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } final AuthenticationToken token = mock(AuthenticationToken.class); final String principal = randomAlphaOfLength(5); when(token.principal()).thenReturn(principal); @@ -1028,13 +1226,22 @@ public void testRealmAuthenticateTerminateAuthenticationProcessWithException() { assertThat(e.getMessage(), is("error attempting to authenticate request")); assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme)); } - verify(auditTrail).authenticationFailed(reqId, secondRealm.name(), token, "_action", transportRequest); - verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), secondRealm.name(), token, "_action", transportRequest); + verify(auditTrail).authenticationFailed(reqId.get(), token, "_action", transportRequest); verifyNoMoreInteractions(auditTrail); } public void testRealmAuthenticateGracefulTerminateAuthenticationProcess() { - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } final AuthenticationToken token = mock(AuthenticationToken.class); final String principal = randomAlphaOfLength(5); when(token.principal()).thenReturn(principal); @@ -1047,8 +1254,13 @@ public void testRealmAuthenticateGracefulTerminateAuthenticationProcess() { expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null)); assertThat(e.getMessage(), is("unable to authenticate user [" + principal + "] for action [_action]")); assertThat(e.getHeader("WWW-Authenticate"), contains(basicScheme)); - verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", transportRequest); - verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), firstRealm.name(), token, "_action", transportRequest); + verify(auditTrail).authenticationFailed(reqId.get(), token, "_action", transportRequest); verifyNoMoreInteractions(auditTrail); } @@ -1059,13 +1271,22 @@ public void testRealmAuthenticateThrowingException() throws Exception { when(secondRealm.supports(token)).thenReturn(true); doThrow(authenticationError("realm doesn't like authenticate")) .when(secondRealm).authenticate(eq(token), any(ActionListener.class)); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } try { authenticateBlocking("_action", transportRequest, null); fail("exception should bubble out"); } catch (ElasticsearchException e) { assertThat(e.getMessage(), is("realm doesn't like authenticate")); - verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), token, "_action", transportRequest); } } @@ -1081,7 +1302,7 @@ public void testRealmAuthenticateThrowingExceptionRest() throws Exception { fail("exception should bubble out"); } catch (ElasticsearchSecurityException e) { assertThat(e.getMessage(), is("realm doesn't like authenticate")); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationFailed(reqId, token, restRequest); } } @@ -1096,14 +1317,22 @@ public void testRealmLookupThrowingException() throws Exception { mockRealmLookupReturnsNull(firstRealm, "run_as"); doThrow(authenticationError("realm doesn't want to lookup")) .when(secondRealm).lookupUser(eq("run_as"), any(ActionListener.class)); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); - + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } try { authenticateBlocking("_action", transportRequest, null); fail("exception should bubble out"); } catch (ElasticsearchException e) { assertThat(e.getMessage(), is("realm doesn't want to lookup")); - verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), token, "_action", transportRequest); } } @@ -1122,12 +1351,18 @@ public void testRealmLookupThrowingExceptionRest() throws Exception { fail("exception should bubble out"); } catch (ElasticsearchException e) { assertThat(e.getMessage(), is("realm doesn't want to lookup")); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationFailed(reqId, token, restRequest); } } public void testRunAsLookupSameRealm() throws Exception { + boolean testTransportRequest = randomBoolean(); + boolean requestIdAlreadyPresent = randomBoolean() && testTransportRequest; + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } AuthenticationToken token = mock(AuthenticationToken.class); when(token.principal()).thenReturn(randomAlphaOfLength(5)); threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as"); @@ -1162,13 +1397,16 @@ public void testRunAsLookupSameRealm() throws Exception { assertEquals(user.email(), authUser.email()); assertEquals(user.enabled(), authUser.enabled()); assertEquals(user.fullName(), authUser.fullName()); - - + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + expectAuditRequestId(threadContext); + } setCompletedToTrue(completed); }, this::logAndFail); // we do not actually go async - if (randomBoolean()) { + if (testTransportRequest) { service.authenticate("_action", transportRequest, true, listener); } else { service.authenticate(restRequest, listener); @@ -1178,6 +1416,12 @@ public void testRunAsLookupSameRealm() throws Exception { @SuppressWarnings("unchecked") public void testRunAsLookupDifferentRealm() throws Exception { + boolean testTransportRequest = randomBoolean(); + boolean requestIdAlreadyPresent = randomBoolean() && testTransportRequest; + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } AuthenticationToken token = mock(AuthenticationToken.class); when(token.principal()).thenReturn(randomAlphaOfLength(5)); threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as"); @@ -1203,11 +1447,16 @@ public void testRunAsLookupDifferentRealm() throws Exception { assertThat(authenticated.principal(), is("looked up user")); assertThat(authenticated.roles(), arrayContaining("some role")); assertThreadContextContainsAuthentication(result); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + expectAuditRequestId(threadContext); + } setCompletedToTrue(completed); }, this::logAndFail); // call service asynchronously but it doesn't actually go async - if (randomBoolean()) { + if (testTransportRequest) { service.authenticate("_action", transportRequest, true, listener); } else { service.authenticate(restRequest, listener); @@ -1228,7 +1477,7 @@ public void testRunAsWithEmptyRunAsUsernameRest() throws Exception { authenticateBlocking(restRequest); fail("exception should be thrown"); } catch (ElasticsearchException e) { - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).runAsDenied(eq(reqId), any(Authentication.class), eq(restRequest), eq(EmptyAuthorizationInfo.INSTANCE)); verifyNoMoreInteractions(auditTrail); } @@ -1239,7 +1488,11 @@ public void testRunAsWithEmptyRunAsUsername() throws Exception { when(token.principal()).thenReturn(randomAlphaOfLength(5)); User user = new User("lookup user", new String[]{"user"}); threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, ""); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); mockAuthenticate(secondRealm, token, user); @@ -1248,7 +1501,12 @@ public void testRunAsWithEmptyRunAsUsername() throws Exception { authenticateBlocking("_action", transportRequest, null); fail("exception should be thrown"); } catch (ElasticsearchException e) { - verify(auditTrail).runAsDenied(eq(reqId), any(Authentication.class), eq("_action"), eq(transportRequest), + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).runAsDenied(eq(reqId.get()), any(Authentication.class), eq("_action"), eq(transportRequest), eq(EmptyAuthorizationInfo.INSTANCE)); verifyNoMoreInteractions(auditTrail); } @@ -1259,7 +1517,11 @@ public void testAuthenticateTransportDisabledRunAsUser() throws Exception { AuthenticationToken token = mock(AuthenticationToken.class); when(token.principal()).thenReturn(randomAlphaOfLength(5)); threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as"); - final String reqId = AuditUtil.getOrGenerateRequestId(threadContext); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } when(secondRealm.token(threadContext)).thenReturn(token); when(secondRealm.supports(token)).thenReturn(true); mockAuthenticate(secondRealm, token, new User("lookup user", new String[]{"user"})); @@ -1272,7 +1534,12 @@ public void testAuthenticateTransportDisabledRunAsUser() throws Exception { User fallback = randomBoolean() ? SystemUser.INSTANCE : null; ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, fallback)); - verify(auditTrail).authenticationFailed(reqId, token, "_action", transportRequest); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } + verify(auditTrail).authenticationFailed(reqId.get(), token, "_action", transportRequest); verifyNoMoreInteractions(auditTrail); assertAuthenticationException(e); } @@ -1294,7 +1561,7 @@ public void testAuthenticateRestDisabledRunAsUser() throws Exception { ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking(restRequest)); - String reqId = expectAuditRequestId(); + String reqId = expectAuditRequestId(threadContext); verify(auditTrail).authenticationFailed(reqId, token, restRequest); verifyNoMoreInteractions(auditTrail); assertAuthenticationException(e); @@ -1319,6 +1586,11 @@ public void testAuthenticateWithToken() throws Exception { when(securityIndex.indexExists()).thenReturn(true); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { threadContext.putHeader("Authorization", "Bearer " + token); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); @@ -1326,8 +1598,13 @@ public void testAuthenticateWithToken() throws Exception { assertThat(result.getAuthenticatedBy(), is(notNullValue())); assertThat(result.getAuthenticatedBy().getName(), is("realm")); // TODO implement equals assertThat(result.getAuthenticationType(), is(AuthenticationType.TOKEN)); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } setCompletedToTrue(completed); - verify(auditTrail).authenticationSuccess(anyString(), eq(result), eq("_action"), same(transportRequest)); + verify(auditTrail).authenticationSuccess(eq(reqId.get()), eq(result), eq("_action"), same(transportRequest)); }, this::logAndFail)); } assertTrue(completed.get()); @@ -1345,8 +1622,13 @@ public void testInvalidToken() throws Exception { final CountDownLatch latch = new CountDownLatch(1); final Authentication expected = new Authentication(user, new RealmRef(firstRealm.name(), firstRealm.type(), "authc_test"), null); AtomicBoolean success = new AtomicBoolean(false); + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { threadContext.putHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(randomBytes)); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { assertThat(result, notNullValue()); assertThat(result.getUser(), is(user)); @@ -1354,9 +1636,19 @@ public void testInvalidToken() throws Exception { assertThat(result.getAuthenticatedBy(), is(notNullValue())); assertThreadContextContainsAuthentication(result); assertEquals(expected, result); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } success.set(true); latch.countDown(); }, e -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } else { + reqId.set(expectAuditRequestId(threadContext)); + } if (e instanceof IllegalStateException) { assertThat(e.getMessage(), containsString("array length must be <= to " + ArrayUtil.MAX_ARRAY_LENGTH + " but was: ")); latch.countDown(); @@ -1381,7 +1673,7 @@ public void testInvalidToken() throws Exception { latch.await(); if (success.get()) { final String realmName = firstRealm.name(); - verify(auditTrail).authenticationSuccess(anyString(), eq(expected), eq("_action"), same(transportRequest)); + verify(auditTrail).authenticationSuccess(eq(reqId.get()), eq(expected), eq("_action"), same(transportRequest)); } verifyNoMoreInteractions(auditTrail); } @@ -1407,9 +1699,17 @@ public void testExpiredToken() throws Exception { }).when(securityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } threadContext.putHeader("Authorization", "Bearer " + token); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null)); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } assertEquals(RestStatus.UNAUTHORIZED, e.status()); assertEquals("token expired", e.getMessage()); } @@ -1417,10 +1717,18 @@ public void testExpiredToken() throws Exception { public void testApiKeyAuthInvalidHeader() { try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } final String invalidHeader = randomFrom("apikey", "apikey ", "apikey foo"); threadContext.putHeader("Authorization", invalidHeader); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null)); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } assertEquals(RestStatus.UNAUTHORIZED, e.status()); assertThat(e.getMessage(), containsString("missing authentication credentials")); } @@ -1465,12 +1773,21 @@ public void testApiKeyAuth() { }).when(client).get(any(GetRequest.class), any(ActionListener.class)); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } threadContext.putHeader("Authorization", headerValue); - final Authentication authentication = authenticateBlocking("_action", transportRequest, null); - assertThat(authentication.getUser().principal(), is("johndoe")); - assertThat(authentication.getUser().fullName(), is("john doe")); - assertThat(authentication.getUser().email(), is("john@doe.com")); - assertThat(authentication.getAuthenticationType(), is(AuthenticationType.API_KEY)); + Tuple result = authenticateBlocking("_action", transportRequest, null); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } + assertThat(expectAuditRequestId(threadContext), is(result.v2())); + assertThat(result.v1().getUser().principal(), is("johndoe")); + assertThat(result.v1().getUser().fullName(), is("john doe")); + assertThat(result.v1().getUser().email(), is("john@doe.com")); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.API_KEY)); } } @@ -1507,9 +1824,17 @@ public void testExpiredApiKey() { }).when(client).get(any(GetRequest.class), any(ActionListener.class)); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { + boolean requestIdAlreadyPresent = randomBoolean(); + SetOnce reqId = new SetOnce<>(); + if (requestIdAlreadyPresent) { + reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } threadContext.putHeader("Authorization", headerValue); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> authenticateBlocking("_action", transportRequest, null)); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } assertEquals(RestStatus.UNAUTHORIZED, e.status()); } } @@ -1562,23 +1887,55 @@ private void mockAuthenticate(Realm realm, AuthenticationToken token, Exception }).when(realm).authenticate(eq(token), any(ActionListener.class)); } - private Authentication authenticateBlocking(RestRequest restRequest) { - PlainActionFuture future = new PlainActionFuture<>(); + private Tuple authenticateBlocking(RestRequest restRequest) { + SetOnce reqId = new SetOnce<>(); + PlainActionFuture future = new PlainActionFuture<>() { + @Override + public void onResponse(Authentication result) { + reqId.set(expectAuditRequestId(threadContext)); + assertThat(new AuthenticationContextSerializer().getAuthentication(threadContext), is(result)); + super.onResponse(result); + } + + @Override + public void onFailure(Exception e) { + reqId.set(expectAuditRequestId(threadContext)); + super.onFailure(e); + } + }; service.authenticate(restRequest, future); - return future.actionGet(); + Authentication authentication = future.actionGet(); + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + return new Tuple<>(authentication, reqId.get()); } - private Authentication authenticateBlocking(String action, TransportRequest transportRequest, User fallbackUser) { - PlainActionFuture future = new PlainActionFuture<>(); + private Tuple authenticateBlocking(String action, TransportRequest transportRequest, User fallbackUser) { + SetOnce reqId = new SetOnce<>(); + PlainActionFuture future = new PlainActionFuture<>() { + @Override + public void onResponse(Authentication result) { + reqId.set(expectAuditRequestId(threadContext)); + assertThat(new AuthenticationContextSerializer().getAuthentication(threadContext), is(result)); + super.onResponse(result); + } + + @Override + public void onFailure(Exception e) { + reqId.set(expectAuditRequestId(threadContext)); + super.onFailure(e); + } + }; if (fallbackUser == null) { service.authenticate(action, transportRequest, true, future); } else { service.authenticate(action, transportRequest, fallbackUser, future); } - return future.actionGet(); + Authentication authentication = future.actionGet(); + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + return new Tuple<>(authentication, reqId.get()); } - private String expectAuditRequestId() { + private static String expectAuditRequestId(ThreadContext threadContext) { String reqId = AuditUtil.extractRequestId(threadContext); assertThat(reqId, is(not(emptyOrNullString()))); return reqId; From 6e229a88512026dcc802a6576b4ae87ede9f2369 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 29 Oct 2020 14:24:09 +0200 Subject: [PATCH 11/26] AuthorizationServiceTests --- .../action/filter/SecurityActionFilter.java | 2 + .../security/authz/AuthorizationService.java | 3 +- .../transport/ServerTransportFilter.java | 3 + .../authz/AuthorizationServiceTests.java | 646 ++++++++++-------- 4 files changed, 360 insertions(+), 294 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 580bc2c397268..c75c28bb69ab2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -21,6 +21,7 @@ import org.elasticsearch.action.support.ActionFilterChain; import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.action.support.DestructiveOperations; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.XPackLicenseState; @@ -169,6 +170,7 @@ it to the action without an associated user (not via REST or transport - this is authcService.authenticate(securityAction, request, SystemUser.INSTANCE, ActionListener.wrap((authc) -> { if (authc != null) { + assert Strings.hasText(AuditUtil.extractRequestId(threadContext)); authorizeRequest(authc, securityAction, request, listener); } else if (licenseState.isSecurityEnabled() == false) { listener.onResponse(null); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index 2eb8464444b51..e663476065db0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -434,7 +434,8 @@ private TransportRequest maybeUnwrapRequest(Authentication authentication, Trans return request; } - private boolean isInternalUser(User user) { + // protected for tests + protected static boolean isInternalUser(User user) { return SystemUser.is(user) || XPackUser.is(user) || XPackSecurityUser.is(user) || AsyncSearchUser.is(user); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java index 5b110e57f374a..137fdba910e95 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java @@ -14,6 +14,7 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.open.OpenIndexAction; import org.elasticsearch.action.support.DestructiveOperations; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.transport.TaskTransportChannel; @@ -28,6 +29,7 @@ import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.security.action.SecurityActionMapper; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; @@ -101,6 +103,7 @@ requests from all the nodes are attached with a user (either a serialize final Version version = transportChannel.getVersion(); authcService.authenticate(securityAction, request, true, ActionListener.wrap((authentication) -> { if (authentication != null) { + assert Strings.hasText(AuditUtil.extractRequestId(threadContext)); if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) && SystemUser.is(authentication.getUser()) == false) { securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 61601c054c688..5c9e8ac10dbc0 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authz; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; @@ -168,6 +169,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Predicate; @@ -274,7 +276,8 @@ auditTrailService, new DefaultAuthenticationFailureHandler(Collections.emptyMap( null, Collections.emptySet(), licenseState, new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY))); } - private void authorize(Authentication authentication, String action, TransportRequest request) { + private void authorize(Authentication authentication, String action, TransportRequest request, + BiConsumer postAuthz) { PlainActionFuture done = new PlainActionFuture<>(); PlainActionFuture indicesPermissions = new PlainActionFuture<>(); PlainActionFuture originatingAction = new PlainActionFuture<>(); @@ -298,19 +301,44 @@ private void authorize(Authentication authentication, String action, TransportRe authorizationInfoHeader = mock(AuthorizationInfo.class); threadContext.putTransient(AUTHORIZATION_INFO_KEY, authorizationInfoHeader); } + boolean generateRequestIdBeforeAuthz = (false == AuthorizationService.isInternalUser(authentication.getUser())) || + randomBoolean(); + SetOnce requestId = new SetOnce<>(); + if (generateRequestIdBeforeAuthz) { + requestId.set(AuditUtil.getOrGenerateRequestId(threadContext)); + } ActionListener listener = ActionListener.wrap(response -> { // extract the authorization transient headers from the thread context of the action // that has been authorized originatingAction.onResponse(threadContext.getTransient(ORIGINATING_ACTION_KEY)); authorizationInfo.onResponse(threadContext.getTransient(AUTHORIZATION_INFO_KEY)); indicesPermissions.onResponse(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); + if (generateRequestIdBeforeAuthz) { + assertThat(AuditUtil.extractRequestId(threadContext), is(requestId.get())); + } else { + requestId.set(AuditUtil.extractRequestId(threadContext)); + } done.onResponse(threadContext.getTransient(someRandomHeader)); }, e -> { + if (generateRequestIdBeforeAuthz) { + assertThat(AuditUtil.extractRequestId(threadContext), is(requestId.get())); + } else { + requestId.set(AuditUtil.extractRequestId(threadContext)); + } done.onFailure(e); }); authorizationService.authorize(authentication, action, request, listener); - Object someRandonHeaderValueInListener = done.actionGet(); - assertThat(someRandonHeaderValueInListener, sameInstance(someRandomHeaderValue)); + Object someRandomHeaderValueInListener; + try { + someRandomHeaderValueInListener = done.actionGet(); + } catch (Exception e) { + postAuthz.accept(null, requestId.get()); + throw e; + } + if (generateRequestIdBeforeAuthz) { + assertThat(AuditUtil.extractRequestId(threadContext), is(requestId.get())); + } + assertThat(someRandomHeaderValueInListener, sameInstance(someRandomHeaderValue)); assertThat(threadContext.getTransient(someRandomHeader), sameInstance(someRandomHeaderValue)); // authorization restores any previously existing transient headers if (mockAccessControlHeader != null) { @@ -335,15 +363,16 @@ private void authorize(Authentication authentication, String action, TransportRe if (authorizationInfoHeader != null) { assertThat(authorizationInfo.actionGet(), not(sameInstance(authorizationInfoHeader))); } + assertThat(authorizationInfo.actionGet(), instanceOf(AuthorizationInfo.class)); // except originating action, which is not overwritten if (originatingActionHeader != null) { assertThat(originatingAction.actionGet(), sameInstance(originatingActionHeader)); } + postAuthz.accept((AuthorizationInfo) authorizationInfo.actionGet(), requestId.get()); } public void testActionsForSystemUserIsAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); // A failure would throw an exception final Authentication authentication = createAuthentication(SystemUser.INSTANCE); @@ -362,9 +391,12 @@ public void testActionsForSystemUserIsAuthorized() throws IOException { "indices:admin/seq_no/renew_retention_lease", "indices:admin/settings/update" }; for (String action : actions) { - authorize(authentication, action, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { SystemUser.ROLE_NAME })); + authorize(authentication, action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{SystemUser.ROLE_NAME})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); + + }); } verifyNoMoreInteractions(auditTrail); @@ -373,36 +405,33 @@ public void testActionsForSystemUserIsAuthorized() throws IOException { public void testIndicesActionsForSystemUserWhichAreNotAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = createAuthentication(SystemUser.INSTANCE); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:", request), - "indices:", SystemUser.INSTANCE.principal()); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:"), eq(request), - authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); + () -> authorize(authentication, "indices:", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:"), eq(request), + authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); + }), "indices:", SystemUser.INSTANCE.principal()); verifyNoMoreInteractions(auditTrail); } public void testClusterAdminActionsForSystemUserWhichAreNotAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = createAuthentication(SystemUser.INSTANCE); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "cluster:admin/whatever", request), - "cluster:admin/whatever", SystemUser.INSTANCE.principal()); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/whatever"), eq(request), - authzInfoRoles(new String[] { SystemUser.ROLE_NAME })); + () -> authorize(authentication, "cluster:admin/whatever", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/whatever"), eq(request), + authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); + }), "cluster:admin/whatever", SystemUser.INSTANCE.principal()); verifyNoMoreInteractions(auditTrail); } public void testClusterAdminSnapshotStatusActionForSystemUserWhichIsNotAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = createAuthentication(SystemUser.INSTANCE); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "cluster:admin/snapshot/status", request), - "cluster:admin/snapshot/status", SystemUser.INSTANCE.principal()); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/snapshot/status"), eq(request), - authzInfoRoles(new String[] { SystemUser.ROLE_NAME })); + () -> authorize(authentication, "cluster:admin/snapshot/status", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/snapshot/status"), eq(request), + authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); + }), "cluster:admin/snapshot/status", SystemUser.INSTANCE.principal()); verifyNoMoreInteractions(auditTrail); } @@ -422,13 +451,14 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build final ConfigurableClusterPrivilege[] configurableClusterPrivileges = new ConfigurableClusterPrivilege[] { configurableClusterPrivilege }; - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); RoleDescriptor role = new RoleDescriptor("role1", null, null, null, configurableClusterPrivileges, null, null ,null); roleMap.put("role1", role); - authorize(authentication, DeletePrivilegesAction.NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(DeletePrivilegesAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, DeletePrivilegesAction.NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(DeletePrivilegesAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }); verifyNoMoreInteractions(auditTrail); } @@ -448,15 +478,14 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build final ConfigurableClusterPrivilege[] configurableClusterPrivileges = new ConfigurableClusterPrivilege[] { configurableClusterPrivilege }; - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); RoleDescriptor role = new RoleDescriptor("role1", null, null, null, configurableClusterPrivileges, null, null ,null); roleMap.put("role1", role); assertThrowsAuthorizationException( - () -> authorize(authentication, DeletePrivilegesAction.NAME, request), - DeletePrivilegesAction.NAME, "user1"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(DeletePrivilegesAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, DeletePrivilegesAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/xpack/security/privilege/delete"), + eq(request), authzInfoRoles(new String[]{role.getName()})); + }), DeletePrivilegesAction.NAME, "user1"); verifyNoMoreInteractions(auditTrail); } @@ -464,12 +493,11 @@ public void testNoRolesCausesDenial() throws IOException { final TransportRequest request = new SearchRequest(); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:a", request), - "indices:a", "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), + eq(request), authzInfoRoles(Role.EMPTY.names())); + }), "indices:a", "test user"); verifyNoMoreInteractions(auditTrail); } @@ -478,10 +506,11 @@ public void testUserWithNoRolesCanPerformRemoteSearch() throws IOException { request.indices("other_cluster:index1", "*_cluster:index2", "other_cluster:other_*"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, SearchAction.NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(request), - authzInfoRoles(Role.EMPTY.names())); + authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search"), eq(request), + authzInfoRoles(Role.EMPTY.names())); + }); verifyNoMoreInteractions(auditTrail); } @@ -491,21 +520,21 @@ public void testUserWithNoRolesPerformsRemoteSearchWithScroll() { when(searchScrollRequest.parseScrollId()).thenReturn(parsedScrollId); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); for (final boolean hasLocalIndices: List.of(true, false)) { when(parsedScrollId.hasLocalIndices()).thenReturn(hasLocalIndices); if (hasLocalIndices) { assertThrowsAuthorizationException( - () -> authorize(authentication, SearchScrollAction.NAME, searchScrollRequest), - "indices:data/read/scroll", "test user" + () -> authorize(authentication, SearchScrollAction.NAME, searchScrollRequest, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/scroll"), + eq(searchScrollRequest), authzInfoRoles(Role.EMPTY.names())); + }), "indices:data/read/scroll", "test user" ); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), - eq("indices:data/read/scroll"), eq(searchScrollRequest), - authzInfoRoles(Role.EMPTY.names())); } else { - authorize(authentication, SearchScrollAction.NAME, searchScrollRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchScrollAction.NAME), eq(searchScrollRequest), - authzInfoRoles(Role.EMPTY.names())); + authorize(authentication, SearchScrollAction.NAME, searchScrollRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/scroll"), + eq(searchScrollRequest), authzInfoRoles(Role.EMPTY.names())); + }); } verifyNoMoreInteractions(auditTrail); } @@ -521,12 +550,11 @@ public void testUserWithNoRolesCannotPerformLocalSearch() throws IOException { request.indices("no_such_cluster:index"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, SearchAction.NAME, request), - SearchAction.NAME, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(request), - authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), + eq(request), authzInfoRoles(Role.EMPTY.names())); + }), SearchAction.NAME, "test user"); verifyNoMoreInteractions(auditTrail); } @@ -539,12 +567,11 @@ public void testUserWithNoRolesCanPerformMultiClusterSearch() throws IOException request.indices("local_index", "wildcard_*", "other_cluster:remote_index", "*:foo?"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, SearchAction.NAME, request), - SearchAction.NAME, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(request), - authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), + eq(request), authzInfoRoles(Role.EMPTY.names())); + }), SearchAction.NAME, "test user"); verifyNoMoreInteractions(auditTrail); } @@ -552,12 +579,11 @@ public void testUserWithNoRolesCannotSql() throws IOException { TransportRequest request = new SqlQueryRequest(); Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, SqlQueryAction.NAME, request), - SqlQueryAction.NAME, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SqlQueryAction.NAME), eq(request), - authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, SqlQueryAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/sql"), + eq(request), authzInfoRoles(Role.EMPTY.names())); + }), SqlQueryAction.NAME, "test user"); verifyNoMoreInteractions(auditTrail); } @@ -570,19 +596,17 @@ public void testRemoteIndicesOnlyWorkWithApplicableRequestTypes() throws IOExcep request.indices("other_cluster:index1", "other_cluster:index2"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, DeleteIndexAction.NAME, request), - DeleteIndexAction.NAME, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(DeleteIndexAction.NAME), eq(request), - authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, DeleteIndexAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:admin/delete"), + eq(request), authzInfoRoles(Role.EMPTY.names())); + }), DeleteIndexAction.NAME, "test user"); verifyNoMoreInteractions(auditTrail); } public void testUserWithNoRolesOpenPointInTimeWithRemoteIndices() { final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); for (final boolean hasLocalIndices: List.of(true, false)) { final String[] indices = new String[] { hasLocalIndices ? @@ -596,17 +620,17 @@ public void testUserWithNoRolesOpenPointInTimeWithRemoteIndices() { ); if (hasLocalIndices) { assertThrowsAuthorizationException( - () -> authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest), - "indices:data/read/open_point_in_time", "test user" + () -> authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/open_point_in_time"), + eq(openPointInTimeRequest), authzInfoRoles(Role.EMPTY.names())); + }), "indices:data/read/open_point_in_time", "test user" ); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), - eq("indices:data/read/open_point_in_time"), eq(openPointInTimeRequest), - authzInfoRoles(Role.EMPTY.names())); } else { - authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), - eq("indices:data/read/open_point_in_time"), eq(openPointInTimeRequest), - authzInfoRoles(Role.EMPTY.names())); + authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/open_point_in_time"), + eq(openPointInTimeRequest), authzInfoRoles(Role.EMPTY.names())); + }); } verifyNoMoreInteractions(auditTrail); } @@ -616,11 +640,11 @@ public void testUserWithNoRolesCanClosePointInTime() { final ClosePointInTimeRequest closePointInTimeRequest = new ClosePointInTimeRequest(randomAlphaOfLength(8)); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, ClosePointInTimeAction.NAME, closePointInTimeRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), - eq("indices:data/read/close_point_in_time"), eq(closePointInTimeRequest), - authzInfoRoles(Role.EMPTY.names())); + authorize(authentication, ClosePointInTimeAction.NAME, closePointInTimeRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/close_point_in_time"), + eq(closePointInTimeRequest), authzInfoRoles(Role.EMPTY.names())); + }); verifyNoMoreInteractions(auditTrail); } @@ -631,28 +655,27 @@ public void testUnknownRoleCausesDenial() throws IOException { String action = tuple.v1(); TransportRequest request = tuple.v2(); final Authentication authentication = createAuthentication(new User("test user", "non-existent-role")); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request), - action, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), + eq(request), authzInfoRoles(Role.EMPTY.names())); + }), action, "test user"); verifyNoMoreInteractions(auditTrail); } public void testThatNonIndicesAndNonClusterActionIsDenied() throws IOException { final TransportRequest request = mock(TransportRequest.class); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("test user", "a_all")); final RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); roleMap.put("a_all", role); assertThrowsAuthorizationException( - () -> authorize(authentication, "whatever", request), - "whatever", "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("whatever"), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, "whatever", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("whatever"), + eq(request), authzInfoRoles(new String[]{role.getName()})); + }), "whatever", "test user"); verifyNoMoreInteractions(auditTrail); } @@ -663,28 +686,28 @@ public void testThatRoleWithNoIndicesIsDenied() throws IOException { new Tuple<>(SqlQueryAction.NAME, new SqlQueryRequest())); String action = tuple.v1(); TransportRequest request = tuple.v2(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("test user", "no_indices")); RoleDescriptor role = new RoleDescriptor("no_indices", null, null, null); roleMap.put("no_indices", role); mockEmptyMetadata(); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request), - action, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), + eq(request), authzInfoRoles(new String[]{role.getName()})); + }), action, "test user"); verifyNoMoreInteractions(auditTrail); } public void testElasticUserAuthorizedForNonChangePasswordRequestsWhenNotInSetupMode() throws IOException { final Authentication authentication = createAuthentication(new ElasticUser(true)); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Tuple request = randomCompositeRequest(); - authorize(authentication, request.v1(), request.v2()); - - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(request.v1()), eq(request.v2()), - authzInfoRoles(new String[]{ElasticUser.ROLE_NAME})); + authorize(authentication, request.v1(), request.v2(), (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {ElasticUser.ROLE_NAME})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(request.v1()), + eq(request.v2()), authzInfoRoles(new String[] {ElasticUser.ROLE_NAME})); + }); + verifyNoMoreInteractions(auditTrail); } public void testSearchAgainstEmptyCluster() throws Exception { @@ -702,10 +725,10 @@ public void testSearchAgainstEmptyCluster() throws Exception { true, false)); assertThrowsAuthorizationException( - () -> authorize(authentication, SearchAction.NAME, searchRequest), - SearchAction.NAME, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(searchRequest), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, SearchAction.NAME, searchRequest, (authorizationInfo, ignored) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), + eq(searchRequest), authzInfoRoles(new String[]{role.getName()})); + }), SearchAction.NAME, "test user"); verifyNoMoreInteractions(auditTrail); } @@ -738,42 +761,55 @@ public void testScrollRelatedRequestsAllowed() { final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); - authorize(authentication, ClearScrollAction.NAME, clearScrollRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClearScrollAction.NAME), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, ClearScrollAction.NAME, clearScrollRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/scroll/clear"), + eq(clearScrollRequest), authzInfoRoles(new String[] {role.getName()})); + }); final ParsedScrollId parsedScrollId = mock(ParsedScrollId.class); when(parsedScrollId.hasLocalIndices()).thenReturn(true); final SearchScrollRequest searchScrollRequest = mock(SearchScrollRequest.class); when(searchScrollRequest.parseScrollId()).thenReturn(parsedScrollId); - authorize(authentication, SearchScrollAction.NAME, searchScrollRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchScrollAction.NAME), eq(searchScrollRequest), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, SearchScrollAction.NAME, searchScrollRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/scroll"), + eq(searchScrollRequest), authzInfoRoles(new String[] {role.getName()})); + }); // We have to use a mock request for other Scroll actions as the actual requests are package private to SearchTransportService final TransportRequest request = mock(TransportRequest.class); - authorize(authentication, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME), - eq(request), authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[clear_scroll_contexts]"), + eq(request), authzInfoRoles(new String[] {role.getName()})); + }); - authorize(authentication, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME), - eq(request), authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[phase/fetch/id/scroll]"), + eq(request), authzInfoRoles(new String[] {role.getName()})); + }); - authorize(authentication, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME), - eq(request), authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[phase/query+fetch/scroll]"), + eq(request), authzInfoRoles(new String[] {role.getName()})); + }); - authorize(authentication, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.QUERY_SCROLL_ACTION_NAME), - eq(request), authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[phase/query/scroll]"), + eq(request), authzInfoRoles(new String[] {role.getName()})); + }); - authorize(authentication, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME), - eq(request), authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[free_context/scroll]"), + eq(request), authzInfoRoles(new String[] {role.getName()})); + }); verifyNoMoreInteractions(auditTrail); } @@ -784,13 +820,12 @@ public void testAuthorizeIndicesFailures() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:a", request), - "indices:a", "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), + eq(request), authzInfoRoles(new String[]{role.getName()})); + }), "indices:a", "test user"); verifyNoMoreInteractions(auditTrail); verify(clusterService, times(1)).state(); verify(state, times(1)).metadata(); @@ -804,15 +839,14 @@ public void testCreateIndexWithAliasWithoutPermissions() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, CreateIndexAction.NAME, request), - IndicesAliasesAction.NAME, "test user"); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(IndicesAliasesAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, CreateIndexAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(IndicesAliasesAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }), IndicesAliasesAction.NAME, "test user"); verifyNoMoreInteractions(auditTrail); verify(clusterService).state(); verify(state, times(1)).metadata(); @@ -826,14 +860,14 @@ public void testCreateIndexWithAlias() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a", "a2").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - - authorize(authentication, CreateIndexAction.NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:admin/aliases"), eq(request), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, CreateIndexAction.NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:admin/aliases"), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }); verifyNoMoreInteractions(auditTrail); verify(clusterService).state(); verify(state, times(1)).metadata(); @@ -854,7 +888,11 @@ public void testDenialErrorMessagesForSearchAction() throws IOException { TransportRequest request = new SearchRequest("all-1", "read-2", "write-3", "other-4"); ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(authentication, SearchAction.NAME, request)); + () -> authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), + eq(request), authzInfoRoles(new String[]{role.getName()})); + })); + verifyNoMoreInteractions(auditTrail); assertThat(securityException, throwableWithMessage( containsString("[" + SearchAction.NAME + "] is unauthorized for user [" + user.principal() + "] on indices ["))); assertThat(securityException, throwableWithMessage(containsString("write-3"))); @@ -886,7 +924,11 @@ public void testDenialErrorMessagesForBulkIngest() throws Exception { new BulkItemRequest(2, new DeleteRequest(index, "doc-3")) }); - authorize(authentication, TransportShardBulkAction.ACTION_NAME, request); + authorize(authentication, TransportShardBulkAction.ACTION_NAME, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/write/bulk[s]"), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }); MappingUpdatePerformer mappingUpdater = (m, s, l) -> l.onResponse(null); Consumer> waitForMappingUpdate = l -> l.onResponse(null); @@ -920,14 +962,13 @@ public void testDenialForAnonymousUser() throws IOException { RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(anonymousUser); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:a", request), - "indices:a", anonymousUser.principal()); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, "indices:a", request,(authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }), "indices:a", anonymousUser.principal()); verifyNoMoreInteractions(auditTrail); verify(clusterService, times(1)).state(); verify(state, times(1)).metadata(); @@ -949,13 +990,13 @@ public void testDenialForAnonymousUserAuthorizationExceptionDisabled() throws IO RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(authentication, "indices:a", request)); + () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); + })); assertAuthenticationException(securityException, containsString("action [indices:a] requires authentication")); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService, times(1)).state(); verify(state, times(1)).metadata(); @@ -969,15 +1010,15 @@ public void testAuditTrailIsRecordedWhenIndexWildcardThrowsError() throws IOExce new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final IndexNotFoundException nfe = expectThrows( IndexNotFoundException.class, - () -> authorize(authentication, GetIndexAction.NAME, request)); + () -> authorize(authentication, GetIndexAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(GetIndexAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + })); assertThat(nfe.getIndex(), is(notNullValue())); assertThat(nfe.getIndex().getName(), is("not-an-index-*")); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(GetIndexAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService).state(); verify(state, times(1)).metadata(); @@ -985,19 +1026,17 @@ public void testAuditTrailIsRecordedWhenIndexWildcardThrowsError() throws IOExce public void testRunAsRequestWithNoRolesUser() throws IOException { final TransportRequest request = mock(TransportRequest.class); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("run as me", null, new User("test user", "admin"))); assertNotEquals(authentication.getUser().authenticatedUser(), authentication); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, "indices:a", request), - "indices:a", "test user", "run as me"); // run as [run as me] - verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(Role.EMPTY.names())); + () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(Role.EMPTY.names())); + }), "indices:a", "test user", "run as me"); // run as [run as me] verifyNoMoreInteractions(auditTrail); } public void testRunAsRequestWithoutLookedUpBy() throws IOException { - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); AuthenticateRequest request = new AuthenticateRequest("run as me"); roleMap.put("superuser", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); User user = new User("run as me", Strings.EMPTY_ARRAY, new User("test user", new String[]{"superuser"})); @@ -1005,10 +1044,10 @@ public void testRunAsRequestWithoutLookedUpBy() throws IOException { authentication.writeToContext(threadContext); assertNotEquals(user.authenticatedUser(), user); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, AuthenticateAction.NAME, request), - AuthenticateAction.NAME, "test user", "run as me"); // run as [run as me] - verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq(AuthenticateAction.NAME), eq(request), - authzInfoRoles(new String[] { ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName() })); + () -> authorize(authentication, AuthenticateAction.NAME, request, (authorizationInfo, requestId) -> { + verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq(AuthenticateAction.NAME), eq(request), + authzInfoRoles(new String[] { ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName() })); + }), AuthenticateAction.NAME, "test user", "run as me"); // run as [run as me] verifyNoMoreInteractions(auditTrail); } @@ -1021,13 +1060,12 @@ public void testRunAsRequestRunningAsUnAllowedUser() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, new String[]{"not the right user"}); roleMap.put("can run as", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, "indices:a", request), - "indices:a", "test user", "run as me"); - verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }), "indices:a", "test user", "run as me"); verifyNoMoreInteractions(auditTrail); } @@ -1057,20 +1095,19 @@ public void testRunAsRequestWithRunAsUserWithoutPermission() throws IOException } else { mockEmptyMetadata(); } - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, "indices:a", request), - "indices:a", "test user", "run as me"); - verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{runAsRole.getName()})); - if (indexExists) { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{bRole.getName()})); - } else { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(Role.EMPTY.names())); - } + () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{runAsRole.getName()})); + if (indexExists) { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{bRole.getName()})); + } else { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(Role.EMPTY.names())); + } + }), "indices:a", "test user", "run as me"); verifyNoMoreInteractions(auditTrail); } @@ -1094,13 +1131,15 @@ public void testRunAsRequestWithValidPermissions() throws IOException { RoleDescriptor bRole = new RoleDescriptor("b", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("b").privileges("all").build()}, null); roleMap.put("b", bRole); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, "indices:a", request); - verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{runAsRole.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{bRole.getName()})); + authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{bRole.getName()})); + verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{runAsRole.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{bRole.getName()})); + + }); verifyNoMoreInteractions(auditTrail); } @@ -1119,7 +1158,6 @@ public void testGrantAllRestrictedUserCannotExecuteOperationAgainstSecurityIndic .numberOfReplicas(0) .build(),true) .build()); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); List> requests = new ArrayList<>(); requests.add(new Tuple<>(BulkAction.NAME + "[s]", @@ -1154,28 +1192,34 @@ public void testGrantAllRestrictedUserCannotExecuteOperationAgainstSecurityIndic String action = requestTuple.v1(); TransportRequest request = requestTuple.v2(); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request), - action, "all_access_user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }), action, "all_access_user"); verifyNoMoreInteractions(auditTrail); } // we should allow waiting for the health of the index or any index if the user has this permission - ClusterHealthRequest request = new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)); - authorize(authentication, ClusterHealthAction.NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); + ClusterHealthRequest clusterHealthRequest1 = new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, + INTERNAL_SECURITY_MAIN_INDEX_7)); + authorize(authentication, ClusterHealthAction.NAME, clusterHealthRequest1, (authorizationInfo, requestId) -> { + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(clusterHealthRequest1), + authzInfoRoles(new String[]{role.getName()})); + }); // multiple indices - request = new ClusterHealthRequest(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7, "foo", "bar"); - authorize(authentication, ClusterHealthAction.NAME, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); + ClusterHealthRequest clusterHealthRequest2 = new ClusterHealthRequest(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7, + "foo", "bar"); + authorize(authentication, ClusterHealthAction.NAME, clusterHealthRequest2, (authorizationInfo, requestId) -> { + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(clusterHealthRequest2), + authzInfoRoles(new String[]{role.getName()})); + }); verifyNoMoreInteractions(auditTrail); final SearchRequest searchRequest = new SearchRequest("_all"); - authorize(authentication, SearchAction.NAME, searchRequest); + authorize(authentication, SearchAction.NAME, searchRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + }); assertEquals(2, searchRequest.indices().length); assertEquals(IndicesAndAliasesResolver.NO_INDICES_OR_ALIASES_LIST, Arrays.asList(searchRequest.indices())); } @@ -1210,19 +1254,21 @@ public void testMonitoringOperationsAgainstSecurityIndexRequireAllowRestricted() final String action = requestTuple.v1(); final TransportRequest request = requestTuple.v2(); try (StoredContext ignore = threadContext.stashContext()) { - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication restrictedUserAuthn = createAuthentication(new User("restricted_user", "restricted_monitor")); - assertThrowsAuthorizationException(() -> authorize(restrictedUserAuthn, action, request), action, "restricted_user"); - verify(auditTrail).accessDenied(eq(requestId), eq(restrictedUserAuthn), eq(action), eq(request), - authzInfoRoles(new String[] { "restricted_monitor" })); + assertThrowsAuthorizationException(() -> authorize(restrictedUserAuthn, action, request, + ((authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(restrictedUserAuthn), eq(action), eq(request), + authzInfoRoles(new String[] { "restricted_monitor" })); + })), action, "restricted_user"); verifyNoMoreInteractions(auditTrail); } try (StoredContext ignore = threadContext.stashContext()) { - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication unrestrictedUserAuthn = createAuthentication(new User("unrestricted_user", "unrestricted_monitor")); - authorize(unrestrictedUserAuthn, action, request); - verify(auditTrail).accessGranted(eq(requestId), eq(unrestrictedUserAuthn), eq(action), eq(request), - authzInfoRoles(new String[] { "unrestricted_monitor" })); + authorize(unrestrictedUserAuthn, action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{unrestrictedMonitorRole.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(unrestrictedUserAuthn), eq(action), eq(request), + authzInfoRoles(new String[] { "unrestricted_monitor" })); + }); verifyNoMoreInteractions(auditTrail); } } @@ -1241,7 +1287,6 @@ public void testSuperusersCanExecuteOperationAgainstSecurityIndex() throws IOExc .numberOfReplicas(0) .build(), true) .build()); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); List> requests = new ArrayList<>(); requests.add( @@ -1273,9 +1318,12 @@ public void testSuperusersCanExecuteOperationAgainstSecurityIndex() throws IOExc final TransportRequest request = requestTuple.v2(); try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(false)) { final Authentication authentication = createAuthentication(superuser); - authorize(authentication, action, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(superuser.roles())); + authorize(authentication, action, request, ((authorizationInfo, requestId) -> { + assertThat(authorizationInfo, + new RBACAuthorizationInfoRoleMatcher(new String[]{ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(superuser.roles())); + })); } } } @@ -1294,12 +1342,14 @@ public void testSuperusersCanExecuteOperationAgainstSecurityIndexWithWildcard() .numberOfReplicas(0) .build(), true) .build()); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); String action = SearchAction.NAME; SearchRequest request = new SearchRequest("_all"); - authorize(authentication, action, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(superuser.roles())); + authorize(authentication, action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, + new RBACAuthorizationInfoRoleMatcher(new String[]{ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(superuser.roles())); + }); assertThat(request.indices(), arrayContainingInAnyOrder(INTERNAL_SECURITY_MAIN_INDEX_7, SECURITY_MAIN_ALIAS)); } @@ -1311,12 +1361,12 @@ public void testCompositeActionsAreImmediatelyRejected() { final Authentication authentication = createAuthentication(new User("test user", "no_indices")); final RoleDescriptor role = new RoleDescriptor("no_indices", null, null, null); roleMap.put("no_indices", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request), action, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { role.getName() })); + () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { role.getName() })); + }), action, "test user"); verifyNoMoreInteractions(auditTrail); } @@ -1330,24 +1380,25 @@ public void testCompositeActionsIndicesAreNotChecked() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices(randomBoolean() ? "a" : "index").privileges("all").build()}, null); roleMap.put("role", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, action, request); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { role.getName() })); + authorize(authentication, action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, + new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { role.getName() })); + }); verifyNoMoreInteractions(auditTrail); } public void testCompositeActionsMustImplementCompositeIndicesRequest() throws IOException { String action = randomCompositeRequest().v1(); TransportRequest request = mock(TransportRequest.class); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); User user = new User("test user", "role"); roleMap.put("role", new RoleDescriptor("role", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices(randomBoolean() ? "a" : "index").privileges("all").build()}, null)); IllegalStateException illegalStateException = expectThrows(IllegalStateException.class, - () -> authorize(createAuthentication(user), action, request)); + () -> authorize(createAuthentication(user), action, request, (authorizationInfo, requestId) -> {})); assertThat(illegalStateException.getMessage(), containsString("Composite and bulk actions must implement CompositeIndicesRequest")); } @@ -1391,10 +1442,13 @@ public void testCompositeActionsIndicesAreCheckedAtTheShardLevel() throws IOExce AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(false)) { - authorize(createAuthentication(userAllowed), action, request); + authorize(createAuthentication(userAllowed), action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, + new RBACAuthorizationInfoRoleMatcher(new String[]{"roleAllowed"})); + }); } assertThrowsAuthorizationException( - () -> authorize(createAuthentication(userDenied), action, request), action, "userDenied"); + () -> authorize(createAuthentication(userDenied), action, request, (authorizationInfo, requestId) -> {}), action, "userDenied"); } public void testAuthorizationOfIndividualBulkItems() throws IOException { @@ -1419,29 +1473,29 @@ public void testAuthorizationOfIndividualBulkItems() throws IOException { roleMap.put("my-role", role); mockEmptyMetadata(); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, action, request); - - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(DeleteAction.NAME), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(DeleteAction.NAME), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), - eq(DeleteAction.NAME), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { role.getName() })); // bulk request is allowed + authorize(authentication, action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(DeleteAction.NAME), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(DeleteAction.NAME), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), + eq(DeleteAction.NAME), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { role.getName() })); // bulk request is allowed + }); verifyNoMoreInteractions(auditTrail); } @@ -1462,21 +1516,22 @@ public void testAuthorizationOfIndividualBulkItemsWithDateMath() throws IOExcept final RoleDescriptor role = new RoleDescriptor("my-role", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("datemath-*").privileges("index").build()}, null); roleMap.put("my-role", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); - authorize(authentication, action, request); - - // both deletes should fail - verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), - eq(DeleteAction.NAME), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - // bulk request is allowed - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, action, request, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + // both deletes should fail + verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), + eq(DeleteAction.NAME), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + // bulk request is allowed + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[]{role.getName()})); + }); + verifyNoMoreInteractions(auditTrail); } @@ -1534,10 +1589,11 @@ public void testProxyRequestFailsOnNonProxyAction() { TransportRequest request = TransportRequest.Empty.INSTANCE; DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, request); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); User user = new User("test user", "role"); ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(createAuthentication(user), "indices:some/action", transportRequest)); + () -> authorize(createAuthentication(user), "indices:some/action", transportRequest, (authorizationInfo, requestId) -> { + + })); assertThat(ese.getCause(), instanceOf(IllegalStateException.class)); IllegalStateException illegalStateException = (IllegalStateException) ese.getCause(); assertThat(illegalStateException.getMessage(), @@ -1548,9 +1604,11 @@ public void testProxyRequestFailsOnNonProxyAction() { public void testProxyRequestFailsOnNonProxyRequest() { TransportRequest request = TransportRequest.Empty.INSTANCE; User user = new User("test user", "role"); - AuditUtil.getOrGenerateRequestId(threadContext); ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(createAuthentication(user), TransportActionProxy.getProxyAction("indices:some/action"), request)); + () -> authorize(createAuthentication(user), TransportActionProxy.getProxyAction("indices:some/action"), request, + (authorizationInfo, requestId) -> { + + })); assertThat(ese.getCause(), instanceOf(IllegalStateException.class)); IllegalStateException illegalStateException = (IllegalStateException) ese.getCause(); assertThat(illegalStateException.getMessage(), @@ -1567,12 +1625,12 @@ public void testProxyRequestAuthenticationDenied() throws IOException { final Authentication authentication = createAuthentication(new User("test user", "no_indices")); final RoleDescriptor role = new RoleDescriptor("no_indices", null, null, null); roleMap.put("no_indices", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, action, transportRequest), action, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(proxiedRequest), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(proxiedRequest), + authzInfoRoles(new String[]{role.getName()})); + }), action, "test user"); verifyNoMoreInteractions(auditTrail); } @@ -1581,7 +1639,6 @@ public void testProxyRequestAuthenticationGrantedWithAllPrivileges() { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); @@ -1589,9 +1646,11 @@ public void testProxyRequestAuthenticationGrantedWithAllPrivileges() { final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); final TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, clearScrollRequest); final String action = TransportActionProxy.getProxyAction(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); - authorize(authentication, action, transportRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); + }); } public void testProxyRequestAuthenticationGranted() { @@ -1601,14 +1660,15 @@ public void testProxyRequestAuthenticationGranted() { roleMap.put("a_all", role); mockEmptyMetadata(); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); final TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, clearScrollRequest); final String action = TransportActionProxy.getProxyAction(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); - authorize(authentication, action, transportRequest); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); + authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { + assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); + }); } public void testProxyRequestAuthenticationDeniedWithReadPrivileges() throws IOException { @@ -1616,16 +1676,16 @@ public void testProxyRequestAuthenticationDeniedWithReadPrivileges() throws IOEx final RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("read").build()}, null); roleMap.put("a_all", role); - final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, clearScrollRequest); String action = TransportActionProxy.getProxyAction(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); assertThrowsAuthorizationException( - () -> authorize(authentication, action, transportRequest), action, "test user"); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); + () -> authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); + }), action, "test user"); } public void testAuthorizationEngineSelection() { From 43f3644da4020c794f202ddfb5df74bfd6c1a99b Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 29 Oct 2020 14:52:20 +0200 Subject: [PATCH 12/26] ServerTransportFilterTests --- .../transport/ServerTransportFilterTests.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java index f8608d94001a7..1c5f94fd903e0 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.security.user.XPackUser; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.Before; @@ -57,6 +58,8 @@ public class ServerTransportFilterTests extends ESTestCase { private TransportChannel channel; private boolean failDestructiveOperations; private DestructiveOperations destructiveOperations; + private ThreadContext threadContext; + private ServerTransportFilter serverTransportFilter; @Before public void init() throws Exception { @@ -67,9 +70,13 @@ public void init() throws Exception { when(channel.getVersion()).thenReturn(Version.CURRENT); failDestructiveOperations = randomBoolean(); Settings settings = Settings.builder() - .put(DestructiveOperations.REQUIRES_NAME_SETTING.getKey(), failDestructiveOperations).build(); + .put(DestructiveOperations.REQUIRES_NAME_SETTING.getKey(), failDestructiveOperations) + .put("path.home", createTempDir()).build(); destructiveOperations = new DestructiveOperations(settings, new ClusterSettings(settings, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))); + threadContext = new ThreadContext(settings); + serverTransportFilter = new ServerTransportFilter(authcService, authzService, threadContext, false, destructiveOperations, + new SecurityContext(settings, threadContext), new XPackLicenseState(settings, () -> 0)); } public void testInbound() throws Exception { @@ -80,13 +87,13 @@ public void testInbound() throws Exception { doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); + AuditUtil.generateRequestId(threadContext); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(true), any(ActionListener.class)); - ServerTransportFilter filter = getNodeFilter(); PlainActionFuture future = new PlainActionFuture<>(); - filter.inbound("_action", request, channel, future); + serverTransportFilter.inbound("_action", request, channel, future); //future.get(); // don't block it's not called really just mocked verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); } @@ -102,13 +109,13 @@ public void testInboundDestructiveOperations() throws Exception { doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); + AuditUtil.generateRequestId(threadContext); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq(action), eq(request), eq(true), any(ActionListener.class)); - ServerTransportFilter filter = getNodeFilter(); PlainActionFuture listener = mock(PlainActionFuture.class); - filter.inbound(action, request, channel, listener); + serverTransportFilter.inbound(action, request, channel, listener); if (failDestructiveOperations) { verify(listener).onFailure(isA(IllegalArgumentException.class)); verifyNoMoreInteractions(authzService); @@ -122,15 +129,15 @@ public void testInboundAuthenticationException() throws Exception { Exception authE = authenticationError("authc failed"); doAnswer(i -> { final Object[] args = i.getArguments(); + AuditUtil.generateRequestId(threadContext); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onFailure(authE); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(true), any(ActionListener.class)); - ServerTransportFilter filter = getNodeFilter(); try { PlainActionFuture future = new PlainActionFuture<>(); - filter.inbound("_action", request, channel, future); + serverTransportFilter.inbound("_action", request, channel, future); future.actionGet(); fail("expected filter inbound to throw an authentication exception on authentication error"); } catch (ElasticsearchSecurityException e) { @@ -140,11 +147,11 @@ public void testInboundAuthenticationException() throws Exception { } public void testInboundAuthorizationException() throws Exception { - ServerTransportFilter filter = getNodeFilter(); TransportRequest request = mock(TransportRequest.class); Authentication authentication = mock(Authentication.class); doAnswer(i -> { final Object[] args = i.getArguments(); + AuditUtil.generateRequestId(threadContext); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); @@ -156,7 +163,7 @@ public void testInboundAuthorizationException() throws Exception { doThrow(authorizationError("authz failed")) .when(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> { - filter.inbound("_action", request, channel, future); + serverTransportFilter.inbound("_action", request, channel, future); future.actionGet(); }); assertThat(e.getMessage(), equalTo("authz failed")); @@ -165,11 +172,11 @@ public void testInboundAuthorizationException() throws Exception { public void testAllowsNodeActions() throws Exception { final String internalAction = "internal:foo/bar"; final String nodeOrShardAction = "indices:action" + randomFrom("[s]", "[p]", "[r]", "[n]", "[s][p]", "[s][r]", "[f]"); - ServerTransportFilter filter = getNodeFilter(); TransportRequest request = mock(TransportRequest.class); Authentication authentication = new Authentication(new User("test", "superuser"), new RealmRef("test", "test", "node1"), null); doAnswer(i -> { final Object[] args = i.getArguments(); + AuditUtil.generateRequestId(threadContext); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); @@ -183,20 +190,13 @@ public void testAllowsNodeActions() throws Exception { return Void.TYPE; }).when(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(true), any(ActionListener.class)); - filter.inbound(internalAction, request, channel, new PlainActionFuture<>()); + serverTransportFilter.inbound(internalAction, request, channel, new PlainActionFuture<>()); verify(authcService).authenticate(eq(internalAction), eq(request), eq(true), any(ActionListener.class)); verify(authzService).authorize(eq(authentication), eq(internalAction), eq(request), any(ActionListener.class)); - filter.inbound(nodeOrShardAction, request, channel, new PlainActionFuture<>()); + serverTransportFilter.inbound(nodeOrShardAction, request, channel, new PlainActionFuture<>()); verify(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(true), any(ActionListener.class)); verify(authzService).authorize(eq(authentication), eq(nodeOrShardAction), eq(request), any(ActionListener.class)); verifyNoMoreInteractions(authcService, authzService); } - - private ServerTransportFilter getNodeFilter() { - Settings settings = Settings.builder().put("path.home", createTempDir()).build(); - ThreadContext threadContext = new ThreadContext(settings); - return new ServerTransportFilter(authcService, authzService, threadContext, false, destructiveOperations, - new SecurityContext(settings, threadContext), new XPackLicenseState(settings, () -> 0)); - } } From a922aa862bd70eb0137e2e2eabc375f5dfb066ce Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 29 Oct 2020 16:38:35 +0200 Subject: [PATCH 13/26] SecurityActionFilterTests --- .../action/filter/SecurityActionFilter.java | 1 - .../filter/SecurityActionFilterTests.java | 20 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index c75c28bb69ab2..de68c0da68d52 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -31,7 +31,6 @@ import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; -import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.SystemUser; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index b4faf469010eb..032fff1f0af99 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.Before; @@ -60,6 +61,8 @@ public class SecurityActionFilterTests extends ESTestCase { private AuthenticationService authcService; private AuthorizationService authzService; + private AuditTrailService auditTrailService; + private ActionFilterChain chain; private XPackLicenseState licenseState; private SecurityActionFilter filter; private ThreadContext threadContext; @@ -69,6 +72,8 @@ public class SecurityActionFilterTests extends ESTestCase { public void init() throws Exception { authcService = mock(AuthenticationService.class); authzService = mock(AuthorizationService.class); + auditTrailService = mock(AuditTrailService.class); + chain = mock(ActionFilterChain.class); licenseState = mock(XPackLicenseState.class); when(licenseState.isSecurityEnabled()).thenReturn(true); when(licenseState.checkFeature(Feature.SECURITY_STATS_AND_HEALTH)).thenReturn(true); @@ -88,14 +93,13 @@ public void init() throws Exception { when(state.nodes()).thenReturn(nodes); SecurityContext securityContext = new SecurityContext(settings, threadContext); - filter = new SecurityActionFilter(authcService, authzService, mock(AuditTrailService.class), licenseState, threadPool, + filter = new SecurityActionFilter(authcService, authzService, auditTrailService, licenseState, threadPool, securityContext, destructiveOperations); } public void testApply() throws Exception { ActionRequest request = mock(ActionRequest.class); ActionListener listener = mock(ActionListener.class); - ActionFilterChain chain = mock(ActionFilterChain.class); Task task = mock(Task.class); User user = new User("username", "r1", "r2"); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); @@ -142,6 +146,7 @@ public void testApplyAsSystemUser() throws Exception { final boolean hasExistingAccessControl = randomBoolean(); final String action = "internal:foo"; if (hasExistingAuthentication) { + AuditUtil.generateRequestId(threadContext); threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, "foo"); threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); @@ -149,12 +154,14 @@ public void testApplyAsSystemUser() throws Exception { threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_NO_INDICES); } } else { + assertNull(AuditUtil.extractRequestId(threadContext)); assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); } doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; + AuditUtil.generateRequestId(threadContext); callback.onResponse(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); return Void.TYPE; }).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); @@ -191,6 +198,9 @@ public void testApplyDestructiveOperations() throws Exception { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; + AuditUtil.generateRequestId(threadContext); + threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); @@ -222,6 +232,10 @@ public void testActionProcessException() throws Exception { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; + assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); + threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); + AuditUtil.generateRequestId(threadContext); callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); @@ -250,6 +264,8 @@ private void mockAuthentication(ActionRequest request, Authentication authentica ActionListener callback = (ActionListener) args[args.length - 1]; assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); + AuditUtil.generateRequestId(threadContext); callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); From 655bba92ee760cdd9878808c15ef4062e91aca1f Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Fri, 30 Oct 2020 01:09:59 +0200 Subject: [PATCH 14/26] More tests --- .../SecurityServerTransportInterceptor.java | 78 ++++++++++--------- .../filter/SecurityActionFilterTests.java | 64 +++++++++++---- ...curityServerTransportInterceptorTests.java | 75 ++++++++++++++++++ 3 files changed, 165 insertions(+), 52 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index 0b140b9af1cc4..fa6d7bab4318f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -219,7 +219,7 @@ public static class ProfileSecuredRequestHandler imp this.forceExecution = forceExecution; } - AbstractRunnable getReceiveRunnable(T request, TransportChannel channel, Task task) { + private AbstractRunnable getReceiveRunnable(T request, TransportChannel channel, Task task, boolean securityEnabled) { return new AbstractRunnable() { @Override public boolean isForceExecution() { @@ -238,48 +238,49 @@ public void onFailure(Exception e) { @Override protected void doRun() throws Exception { - // extract the requestId and the authentication from the threadContext before executing the action - final String requestId = AuditUtil.extractRequestId(threadContext); - final boolean securityEnabled = licenseState.isSecurityEnabled(); - if (securityEnabled && requestId == null) { - channel.sendResponse(new ElasticsearchSecurityException("requestId is unexpectedly missing")); - return; - } - final Authentication authentication = securityContext.getAuthentication(); - if (securityEnabled && authentication == null) { - channel.sendResponse(new ElasticsearchSecurityException("authn is unexpectedly missing")); - return; - } - handler.messageReceived(request, new TransportChannel() { - - @Override - public String getProfileName() { - return channel.getProfileName(); + if (securityEnabled) { + // extract the requestId and the authentication from the threadContext before executing the action + final String requestId = AuditUtil.extractRequestId(threadContext); + if (requestId == null) { + channel.sendResponse(new ElasticsearchSecurityException("requestId is unexpectedly missing")); + return; } - - @Override - public String getChannelType() { - return channel.getChannelType(); + final Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + channel.sendResponse(new ElasticsearchSecurityException("authn is unexpectedly missing")); + return; } + handler.messageReceived(request, new TransportChannel() { - @Override - public Version getVersion() { - return channel.getVersion(); - } + @Override + public String getProfileName() { + return channel.getProfileName(); + } + + @Override + public String getChannelType() { + return channel.getChannelType(); + } + + @Override + public Version getVersion() { + return channel.getVersion(); + } - @Override - public void sendResponse(TransportResponse response) throws IOException { - if (securityEnabled) { + @Override + public void sendResponse(TransportResponse response) throws IOException { auditTrailService.get().actionResponse(requestId, authentication, action, request, response); + channel.sendResponse(response); } - channel.sendResponse(response); - } - @Override - public void sendResponse(Exception exception) throws IOException { - channel.sendResponse(exception); - } - }, task); + @Override + public void sendResponse(Exception exception) throws IOException { + channel.sendResponse(exception); + } + }, task); + } else { + handler.messageReceived(request, channel, task); + } } }; } @@ -295,9 +296,10 @@ public String toString() { @Override public void messageReceived(T request, TransportChannel channel, Task task) throws Exception { - final AbstractRunnable receiveMessage = getReceiveRunnable(request, channel, task); + final boolean securityEnabled = licenseState.isSecurityEnabled(); + final AbstractRunnable receiveMessage = getReceiveRunnable(request, channel, task, securityEnabled); try (ThreadContext.StoredContext ctx = threadContext.newStoredContext(true)) { - if (licenseState.isSecurityEnabled()) { + if (securityEnabled) { String profile = channel.getProfileName(); ServerTransportFilter filter = profileFilters.get(profile); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index 032fff1f0af99..a817931dbcfbc 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.MockIndicesRequest; import org.elasticsearch.action.admin.indices.close.CloseIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; @@ -19,6 +20,7 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -36,6 +38,7 @@ import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; @@ -46,6 +49,7 @@ import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.INDICES_PERMISSIONS_KEY; import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; @@ -62,6 +66,7 @@ public class SecurityActionFilterTests extends ESTestCase { private AuthenticationService authcService; private AuthorizationService authzService; private AuditTrailService auditTrailService; + private AuditTrail auditTrail; private ActionFilterChain chain; private XPackLicenseState licenseState; private SecurityActionFilter filter; @@ -73,6 +78,8 @@ public void init() throws Exception { authcService = mock(AuthenticationService.class); authzService = mock(AuthorizationService.class); auditTrailService = mock(AuditTrailService.class); + auditTrail = mock(AuditTrail.class); + when(auditTrailService.get()).thenReturn(auditTrail); chain = mock(ActionFilterChain.class); licenseState = mock(XPackLicenseState.class); when(licenseState.isSecurityEnabled()).thenReturn(true); @@ -103,22 +110,27 @@ public void testApply() throws Exception { Task task = mock(Task.class); User user = new User("username", "r1", "r2"); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); - mockAuthentication(request, authentication); + String requestId = UUIDs.randomBase64UUID(); + mockAuthentication(request, authentication, requestId); mockAuthorize(); + ActionResponse actionResponse = mock(ActionResponse.class); + mockChain(task, "_action", request, actionResponse); filter.apply(task, "_action", request, listener, chain); verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - verify(chain).proceed(eq(task), eq("_action"), eq(request), any(ActionListener.class)); + verify(auditTrail).actionResponse(eq(requestId), eq(authentication), eq("_action"), eq(request), eq(actionResponse)); } public void testApplyRestoresThreadContext() throws Exception { ActionRequest request = mock(ActionRequest.class); ActionListener listener = mock(ActionListener.class); - ActionFilterChain chain = mock(ActionFilterChain.class); Task task = mock(Task.class); User user = new User("username", "r1", "r2"); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); - mockAuthentication(request, authentication); + String requestId = UUIDs.randomBase64UUID(); + mockAuthentication(request, authentication, requestId); mockAuthorize(); + ActionResponse actionResponse = mock(ActionResponse.class); + mockChain(task, "_action", request, actionResponse); assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); assertNull(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); @@ -127,7 +139,7 @@ public void testApplyRestoresThreadContext() throws Exception { assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); assertNull(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - verify(chain).proceed(eq(task), eq("_action"), eq(request), any(ActionListener.class)); + verify(auditTrail).actionResponse(eq(requestId), eq(authentication), eq("_action"), eq(request), eq(actionResponse)); } public void testApplyAsSystemUser() throws Exception { @@ -137,9 +149,11 @@ public void testApplyAsSystemUser() throws Exception { Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); SetOnce authenticationSetOnce = new SetOnce<>(); SetOnce accessControlSetOnce = new SetOnce<>(); + SetOnce requestIdOnActionHandler = new SetOnce<>(); ActionFilterChain chain = (task, action, request1, listener1) -> { authenticationSetOnce.set(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); accessControlSetOnce.set(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); + requestIdOnActionHandler.set(AuditUtil.extractRequestId(threadContext)); }; Task task = mock(Task.class); final boolean hasExistingAuthentication = randomBoolean(); @@ -157,11 +171,12 @@ public void testApplyAsSystemUser() throws Exception { assertNull(AuditUtil.extractRequestId(threadContext)); assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); } + SetOnce requestIdFromAuthn = new SetOnce<>(); doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; - AuditUtil.generateRequestId(threadContext); + requestIdFromAuthn.set(AuditUtil.generateRequestId(threadContext)); callback.onResponse(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); return Void.TYPE; }).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); @@ -182,6 +197,7 @@ public void testApplyAsSystemUser() throws Exception { assertNotEquals(authentication, authenticationSetOnce.get()); assertEquals(SystemUser.INSTANCE, authenticationSetOnce.get().getUser()); assertThat(accessControlSetOnce.get(), sameInstance(authzAccessControl)); + assertThat(requestIdOnActionHandler.get(), is(requestIdFromAuthn.get())); } public void testApplyDestructiveOperations() throws Exception { @@ -190,15 +206,17 @@ public void testApplyDestructiveOperations() throws Exception { randomFrom("*", "_all", "test*")); String action = randomFrom(CloseIndexAction.NAME, OpenIndexAction.NAME, DeleteIndexAction.NAME); ActionListener listener = mock(ActionListener.class); - ActionFilterChain chain = mock(ActionFilterChain.class); Task task = mock(Task.class); User user = new User("username", "r1", "r2"); Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); + ActionResponse actionResponse = mock(ActionResponse.class); + mockChain(task, action, request, actionResponse); + SetOnce requestIdFromAuthn = new SetOnce<>(); doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; - AuditUtil.generateRequestId(threadContext); + requestIdFromAuthn.set(AuditUtil.generateRequestId(threadContext)); threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); callback.onResponse(authentication); @@ -213,10 +231,12 @@ public void testApplyDestructiveOperations() throws Exception { filter.apply(task, action, request, listener, chain); if (failDestructiveOperations) { verify(listener).onFailure(isA(IllegalArgumentException.class)); - verifyNoMoreInteractions(authzService, chain); + verifyNoMoreInteractions(authzService, chain, auditTrailService, auditTrail); } else { verify(authzService).authorize(eq(authentication), eq(action), eq(request), any(ActionListener.class)); verify(chain).proceed(eq(task), eq(action), eq(request), any(ActionListener.class)); + verify(auditTrail).actionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq(action), eq(request), + eq(actionResponse)); } } @@ -233,13 +253,20 @@ public void testActionProcessException() throws Exception { assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); - threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); AuditUtil.generateRequestId(threadContext); callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); - doThrow(exception).when(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); + if (randomBoolean()) { + doThrow(exception).when(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); + } else { + doAnswer((i) -> { + ActionListener callback = (ActionListener) i.getArguments()[3]; + callback.onFailure(exception); + return Void.TYPE; + }).when(authzService) + .authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); + } filter.apply(task, "_action", request, listener, chain); verify(listener).onFailure(exception); verifyNoMoreInteractions(chain); @@ -257,7 +284,7 @@ public void testApplyUnlicensed() throws Exception { verify(chain).proceed(eq(task), eq("_action"), eq(request), eq(listener)); } - private void mockAuthentication(ActionRequest request, Authentication authentication) { + private void mockAuthentication(ActionRequest request, Authentication authentication, String requestId) { doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); @@ -265,7 +292,7 @@ private void mockAuthentication(ActionRequest request, Authentication authentica assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); - AuditUtil.generateRequestId(threadContext); + threadContext.putHeader("_xpack_audit_request_id", requestId); callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); @@ -288,4 +315,13 @@ private void mockAuthorize(IndicesAccessControl indicesAccessControl) { .authorize(any(Authentication.class), any(String.class), any(TransportRequest.class), any(ActionListener.class)); } + private void mockChain(Task task, String action, ActionRequest request, ActionResponse actionResponse) { + doAnswer(i -> { + final Object[] args = i.getArguments(); + assertThat(args, arrayWithSize(4)); + ActionListener callback = (ActionListener) args[args.length - 1]; + callback.onResponse(actionResponse); + return Void.TYPE; + }).when(chain).proceed(eq(task), eq(action), eq(request), any(ActionListener.class)); + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java index debf8b488b0fe..647a3fad221b9 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java @@ -5,7 +5,9 @@ */ package org.elasticsearch.xpack.security.transport; +import org.apache.lucene.util.SetOnce; import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.main.MainAction; import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.cluster.ClusterState; @@ -17,15 +19,19 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.VersionUtils; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.Transport.Connection; +import org.elasticsearch.transport.TransportChannel; import org.elasticsearch.transport.TransportException; import org.elasticsearch.transport.TransportInterceptor.AsyncSender; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.transport.TransportRequestHandler; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportResponse.Empty; @@ -34,11 +40,14 @@ import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; +import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.After; @@ -48,10 +57,12 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -65,6 +76,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase { private ThreadPool threadPool; private ThreadContext threadContext; private AuditTrailService auditTrailService; + private AuditTrail auditTrail; private XPackLicenseState xPackLicenseState; private SecurityContext securityContext; private ClusterService clusterService; @@ -77,6 +89,8 @@ public void setUp() throws Exception { clusterService = ClusterServiceUtils.createClusterService(threadPool); threadContext = threadPool.getThreadContext(); auditTrailService = mock(AuditTrailService.class); + auditTrail = mock(AuditTrail.class); + when(auditTrailService.get()).thenReturn(auditTrail); securityContext = spy(new SecurityContext(settings, threadPool.getThreadContext())); xPackLicenseState = mock(XPackLicenseState.class); when(xPackLicenseState.isSecurityEnabled()).thenReturn(true); @@ -449,6 +463,67 @@ public String executor() { assertEquals("value", threadContext.getHeader("key")); } + public void testAuditTransportResponse() throws Exception { + User user = new User(randomAlphaOfLength(4), generateRandomStringArray(2, 4, false)); + Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); + AuthenticationService authenticationService = mock(AuthenticationService.class); + TransportRequest request = mock(TransportRequest.class); + AtomicReference requestIdFromAuthn = new AtomicReference<>(); + doAnswer(i -> { + final Object[] args = i.getArguments(); + assertThat(args, arrayWithSize(4)); + ActionListener callback = (ActionListener) args[args.length - 1]; + requestIdFromAuthn.set(AuditUtil.getOrGenerateRequestId(threadContext)); + threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); + threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); + callback.onResponse(authentication); + return Void.TYPE; + }).when(authenticationService).authenticate(eq("_action"), eq(request), eq(true), any(ActionListener.class)); + AuthorizationService authorizationService = mock(AuthorizationService.class); + doAnswer((i) -> { + ActionListener callback = (ActionListener) i.getArguments()[3]; + callback.onResponse(null); + return Void.TYPE; + }).when(authorizationService) + .authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); + SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, + authenticationService, authorizationService, auditTrailService, xPackLicenseState, + mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); + when(xPackLicenseState.isSecurityEnabled()).thenReturn(true); + TransportRequestHandler transportRequestHandler = mock(TransportRequestHandler.class); + TransportRequestHandler wrappedTransportRequestHandler = interceptor.interceptHandler("_action", randomFrom("same", "generic", + "management"), randomBoolean(), transportRequestHandler); + TransportChannel transportChannel = mock(TransportChannel.class); + when(transportChannel.getProfileName()).thenReturn(randomFrom(TransportService.DIRECT_RESPONSE_PROFILE, "default")); + when(transportChannel.getChannelType()).thenReturn(randomAlphaOfLength(4)); + when(transportChannel.getVersion()).thenReturn(VersionUtils.randomVersion(random())); + Task task = mock(Task.class); + TransportResponse transportResponse = mock(TransportResponse.class); + // "send response" generates the audit message + doAnswer(i -> { + TransportChannel wrappedTransportChannel = (TransportChannel) i.getArguments()[1]; + assertThat(wrappedTransportChannel.getProfileName(), is(transportChannel.getProfileName())); + assertThat(wrappedTransportChannel.getChannelType(), is(transportChannel.getChannelType())); + assertThat(wrappedTransportChannel.getVersion(), is(transportChannel.getVersion())); + wrappedTransportChannel.sendResponse(transportResponse); + return Void.TYPE; + }).when(transportRequestHandler).messageReceived(eq(request), any(TransportChannel.class), eq(task)); + wrappedTransportRequestHandler.messageReceived(request, transportChannel, task); + verify(auditTrail).actionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq("_action"), eq(request), + eq(transportResponse)); + doAnswer(i -> { + TransportChannel wrappedTransportChannel = (TransportChannel) i.getArguments()[1]; + assertThat(wrappedTransportChannel.getProfileName(), is(transportChannel.getProfileName())); + assertThat(wrappedTransportChannel.getChannelType(), is(transportChannel.getChannelType())); + assertThat(wrappedTransportChannel.getVersion(), is(transportChannel.getVersion())); + wrappedTransportChannel.sendResponse(new RuntimeException()); + return Void.TYPE; + }).when(transportRequestHandler).messageReceived(eq(request), any(TransportChannel.class), eq(task)); + wrappedTransportRequestHandler.messageReceived(request, transportChannel, task); + verifyNoMoreInteractions(auditTrail); + } + private String[] randomRoles() { return generateRandomStringArray(3, 10, false, true); } From 8c031671e2aeb5fcd0fc3870792bfe120faeb845 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Fri, 30 Oct 2020 01:23:17 +0200 Subject: [PATCH 15/26] Checkstyle --- .../transport/SecurityServerTransportInterceptorTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java index 647a3fad221b9..102ade0bfea8a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.transport; -import org.apache.lucene.util.SetOnce; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.main.MainAction; From 67069934306f5de51924eb1eff4bf6877b191247 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Wed, 4 Nov 2020 14:19:16 +0200 Subject: [PATCH 16/26] Reuse the `User#isInternal` method in the authorization service --- .../xpack/security/authz/AuthorizationService.java | 13 +++---------- .../security/authz/AuthorizationServiceTests.java | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index dc859475693ad..bd245d73b0dbd 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -61,11 +61,8 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.user.AnonymousUser; -import org.elasticsearch.xpack.core.security.user.AsyncSearchUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; -import org.elasticsearch.xpack.core.security.user.XPackSecurityUser; -import org.elasticsearch.xpack.core.security.user.XPackUser; import org.elasticsearch.xpack.security.audit.AuditLevel; import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.audit.AuditTrailService; @@ -95,6 +92,7 @@ import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.INDICES_PERMISSIONS_KEY; import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.ORIGINATING_ACTION_KEY; import static org.elasticsearch.xpack.core.security.support.Exceptions.authorizationError; +import static org.elasticsearch.xpack.core.security.user.User.isInternal; import static org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME; public class AuthorizationService { @@ -188,7 +186,7 @@ public void authorize(final Authentication authentication, final String action, if (auditId == null) { // We would like to assert that there is an existing request-id, but if this is a system action, then that might not be // true because the request-id is generated during authentication - if (isInternalUser(authentication.getUser())) { + if (isInternal(authentication.getUser())) { auditId = AuditUtil.getOrGenerateRequestId(threadContext); } else { auditTrailService.get().tamperedRequest(null, authentication, action, originalRequest); @@ -386,7 +384,7 @@ AuthorizationEngine getAuthorizationEngine(final Authentication authentication) private AuthorizationEngine getAuthorizationEngineForUser(final User user) { if (rbacEngine != authorizationEngine && licenseState.isSecurityEnabled() && licenseState.checkFeature(Feature.SECURITY_AUTHORIZATION_ENGINE)) { - if (ClientReservedRealm.isReserved(user.principal(), settings) || isInternalUser(user)) { + if (ClientReservedRealm.isReserved(user.principal(), settings) || isInternal(user)) { return rbacEngine; } else { return authorizationEngine; @@ -437,11 +435,6 @@ private TransportRequest maybeUnwrapRequest(Authentication authentication, Trans return request; } - // protected for tests - protected static boolean isInternalUser(User user) { - return SystemUser.is(user) || XPackUser.is(user) || XPackSecurityUser.is(user) || AsyncSearchUser.is(user); - } - private void authorizeRunAs(final RequestInfo requestInfo, final AuthorizationInfo authzInfo, final ActionListener listener) { final Authentication authentication = requestInfo.getAuthentication(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 5c9e8ac10dbc0..4cf0e2c1d932a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -301,7 +301,7 @@ private void authorize(Authentication authentication, String action, TransportRe authorizationInfoHeader = mock(AuthorizationInfo.class); threadContext.putTransient(AUTHORIZATION_INFO_KEY, authorizationInfoHeader); } - boolean generateRequestIdBeforeAuthz = (false == AuthorizationService.isInternalUser(authentication.getUser())) || + boolean generateRequestIdBeforeAuthz = (false == User.isInternal(authentication.getUser())) || randomBoolean(); SetOnce requestId = new SetOnce<>(); if (generateRequestIdBeforeAuthz) { From 1b5b65ebc207be85640024017564e01c4e9ba9b6 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 8 Dec 2020 00:04:48 +0200 Subject: [PATCH 17/26] tests afte merge conflict --- .../authc/AuthenticationServiceTests.java | 34 +++++++++---------- .../authz/AuthorizationServiceTests.java | 4 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 1894c69c128c0..54ca2093f5e39 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -655,22 +655,20 @@ public void testAuthenticationInContextAndHeader() throws Exception { reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); } - service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { - if (requestIdAlreadyPresent) { - assertThat(expectAuditRequestId(threadContext), is(reqId.get())); - } - assertThat(expectAuditRequestId(threadContext), is(result.v2())); - assertThat(result, notNullValue()); - assertThat(result.v1().getUser(), is(user)); - assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); - - String userStr = threadContext.getHeader(AuthenticationField.AUTHENTICATION_KEY); - assertThat(userStr, notNullValue()); - assertThat(userStr, equalTo("_signed_auth")); - Authentication ctxAuth = threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY); - assertThat(ctxAuth, is(result)); - verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result), eq(threadContext)); - }, this::logAndFail)); + Tuple result = authenticateBlocking("_action", transportRequest, null); + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } + assertThat(expectAuditRequestId(threadContext), is(result.v2())); + assertThat(result, notNullValue()); + assertThat(result.v1().getUser(), is(user)); + assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); + + String userStr = threadContext.getHeader(AuthenticationField.AUTHENTICATION_KEY); + assertThat(userStr, notNullValue()); + Authentication ctxAuth = threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY); + assertThat(ctxAuth, is(result)); + verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result.v1()), eq(threadContext)); } public void testAuthenticateTransportAnonymous() throws Exception { @@ -1072,7 +1070,7 @@ public void testAnonymousUserRest() throws Exception { assertThat(expectAuditRequestId(threadContext), is(result.v2())); verify(auditTrail).authenticationSuccess(result.v2(), result.v1(), request); verifyNoMoreInteractions(auditTrail); - verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result), eq(threadContext)); + verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result.v1()), eq(threadContext)); } public void testAuthenticateRestRequestDisallowAnonymous() throws Exception { @@ -1846,7 +1844,7 @@ public void testApiKeyAuth() { assertThat(result.v1().getUser().fullName(), is("john doe")); assertThat(result.v1().getUser().email(), is("john@doe.com")); assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.API_KEY)); - verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(authentication), eq(threadContext)); + verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result.v1()), eq(threadContext)); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 2677615df9dfe..7a27bb19cc6d0 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -1837,8 +1837,8 @@ public void testOperatorPrivileges() { AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("user1", "role1")); assertThrowsAuthorizationException( - () -> authorize(authentication, "cluster:admin/whatever", mock(TransportRequest.class)), - "cluster:admin/whatever", "user1"); + () -> authorize(authentication, "cluster:admin/whatever", mock(TransportRequest.class), mock(BiConsumer.class)), + "cluster:admin/whatever", "user1"); // The operator related exception is verified in the authorize(...) call verifyZeroInteractions(auditTrail); } From e7383c50698e949ca1ac92b603b85220f864776a Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 8 Dec 2020 00:27:48 +0200 Subject: [PATCH 18/26] wrong merge choice --- .../xpack/security/authc/AuthenticationServiceTests.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 54ca2093f5e39..69aaf927dcdd8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -659,15 +659,10 @@ public void testAuthenticationInContextAndHeader() throws Exception { if (requestIdAlreadyPresent) { assertThat(expectAuditRequestId(threadContext), is(reqId.get())); } - assertThat(expectAuditRequestId(threadContext), is(result.v2())); assertThat(result, notNullValue()); + assertThat(expectAuditRequestId(threadContext), is(result.v2())); assertThat(result.v1().getUser(), is(user)); assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); - - String userStr = threadContext.getHeader(AuthenticationField.AUTHENTICATION_KEY); - assertThat(userStr, notNullValue()); - Authentication ctxAuth = threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY); - assertThat(ctxAuth, is(result)); verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result.v1()), eq(threadContext)); } From 14425d56c999750b2ef08942af4532dc459c154c Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 8 Dec 2020 00:35:09 +0200 Subject: [PATCH 19/26] Good merge choice --- .../authc/AuthenticationServiceTests.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 69aaf927dcdd8..4b567b9c4c17f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -655,15 +655,20 @@ public void testAuthenticationInContextAndHeader() throws Exception { reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); } - Tuple result = authenticateBlocking("_action", transportRequest, null); - if (requestIdAlreadyPresent) { - assertThat(expectAuditRequestId(threadContext), is(reqId.get())); - } - assertThat(result, notNullValue()); - assertThat(expectAuditRequestId(threadContext), is(result.v2())); - assertThat(result.v1().getUser(), is(user)); - assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); - verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result.v1()), eq(threadContext)); + service.authenticate("_action", transportRequest, true, ActionListener.wrap(result -> { + if (requestIdAlreadyPresent) { + assertThat(expectAuditRequestId(threadContext), is(reqId.get())); + } + assertThat(result, notNullValue()); + assertThat(result.getUser(), is(user)); + assertThat(result.getAuthenticationType(), is(AuthenticationType.REALM)); + + String userStr = threadContext.getHeader(AuthenticationField.AUTHENTICATION_KEY); + assertThat(userStr, notNullValue()); + Authentication ctxAuth = threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY); + assertThat(ctxAuth, is(result)); + verify(operatorPrivilegesService).maybeMarkOperatorUser(eq(result), eq(threadContext)); + }, this::logAndFail)); } public void testAuthenticateTransportAnonymous() throws Exception { From a730658647ddcaaad3f3159f127e9b37312be40d Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 8 Dec 2020 01:14:56 +0200 Subject: [PATCH 20/26] Move chain.proceed down --- .../action/filter/SecurityActionFilter.java | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index de68c0da68d52..1d45ba9176be3 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -88,38 +88,19 @@ public void app if (licenseState.isSecurityEnabled()) { final ActionListener contextPreservingListener = ContextPreservingActionListener.wrapPreservingContext(listener, threadContext); - final ActionListener postAuthzListener = ActionListener.delegateFailure(contextPreservingListener, - (ignore, aVoid) -> { - // extract the requestId and the authentication from the threadContext before executing the action - final String requestId = AuditUtil.extractRequestId(threadContext); - if (requestId == null) { - contextPreservingListener.onFailure(new ElasticsearchSecurityException("requestId is unexpectedly missing")); - return; - } - final Authentication authentication = securityContext.getAuthentication(); - if (authentication == null) { - contextPreservingListener.onFailure(new ElasticsearchSecurityException("authn is unexpectedly missing")); - return; - } - chain.proceed(task, action, request, ActionListener.delegateFailure(contextPreservingListener, - (ignore2, response) -> { - auditTrailService.get().actionResponse(requestId, authentication, action, request, response); - contextPreservingListener.onResponse(response); - })); - }); final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action); try { if (useSystemUser) { securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> { - applyInternal(action, request, postAuthzListener); + applyInternal(task, chain, action, request, contextPreservingListener); }, Version.CURRENT); } else if (AuthorizationUtils.shouldSetUserBasedOnActionOrigin(threadContext)) { AuthorizationUtils.switchUserBasedOnActionOriginAndExecute(threadContext, securityContext, (original) -> { - applyInternal(action, request, postAuthzListener); + applyInternal(task, chain, action, request, contextPreservingListener); }); } else { try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(true)) { - applyInternal(action, request, postAuthzListener); + applyInternal(task, chain, action, request, contextPreservingListener); } } } catch (Exception e) { @@ -144,12 +125,13 @@ public int order() { return Integer.MIN_VALUE; } - private void applyInternal(String action, Request request, ActionListener listener) { + private void applyInternal(Task task, ActionFilterChain chain, String action, Request request, ActionListener listener) { if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) { IndicesRequest indicesRequest = (IndicesRequest) request; try { destructiveOperations.failDestructive(indicesRequest.indices()); - } catch(IllegalArgumentException e) { + } catch (IllegalArgumentException e) { listener.onFailure(e); return; } @@ -169,8 +151,16 @@ it to the action without an associated user (not via REST or transport - this is authcService.authenticate(securityAction, request, SystemUser.INSTANCE, ActionListener.wrap((authc) -> { if (authc != null) { - assert Strings.hasText(AuditUtil.extractRequestId(threadContext)); - authorizeRequest(authc, securityAction, request, listener); + final String requestId = AuditUtil.extractRequestId(threadContext); + assert Strings.hasText(requestId); + authorizeRequest(authc, securityAction, request, ActionListener.delegateFailure(listener, + (ignore, aVoid) -> { + chain.proceed(task, action, request, ActionListener.delegateFailure(listener, + (ignore2, response) -> { + auditTrailService.get().actionResponse(requestId, authc, action, request, response); + listener.onResponse(response); + })); + })); } else if (licenseState.isSecurityEnabled() == false) { listener.onResponse(null); } else { @@ -179,8 +169,9 @@ it to the action without an associated user (not via REST or transport - this is }, listener::onFailure)); } + private void authorizeRequest(Authentication authentication, String securityAction, Request request, - ActionListener listener) { + ActionListener listener) { if (authentication == null) { listener.onFailure(new IllegalArgumentException("authentication must be non null for authorization")); } else { From c6d1a74aaab1ab9fc47f930a302fda8f30aae18d Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 8 Dec 2020 13:21:11 +0200 Subject: [PATCH 21/26] Remove line --- .../xpack/security/action/filter/SecurityActionFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 1d45ba9176be3..0622869cc0485 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -169,7 +169,6 @@ it to the action without an associated user (not via REST or transport - this is }, listener::onFailure)); } - private void authorizeRequest(Authentication authentication, String securityAction, Request request, ActionListener listener) { if (authentication == null) { From 3926adac4a1439a728778ae37b9e453f1b7e37f5 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Wed, 9 Dec 2020 21:01:13 +0200 Subject: [PATCH 22/26] Checkstyle --- .../xpack/security/action/filter/SecurityActionFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 0622869cc0485..e1b59c76afcf6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -8,7 +8,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; From e5ddb6e2313d840c12e35e1b08ab961bc3049cf1 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 15 Dec 2020 11:04:17 +0200 Subject: [PATCH 23/26] Only intercept the action filter --- .../xpack/security/Security.java | 2 +- .../action/filter/SecurityActionFilter.java | 3 +- .../xpack/security/audit/AuditTrail.java | 9 +- .../security/audit/AuditTrailService.java | 16 +- .../audit/logfile/LoggingAuditTrail.java | 13 +- .../SecurityServerTransportInterceptor.java | 68 +- .../transport/ServerTransportFilter.java | 3 - .../filter/SecurityActionFilterTests.java | 7 +- .../authz/AuthorizationServiceTests.java | 648 ++++++++---------- ...curityServerTransportInterceptorTests.java | 109 +-- .../transport/ServerTransportFilterTests.java | 38 +- 11 files changed, 362 insertions(+), 554 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 5aa308972bb16..3002a51fd59fd 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -519,7 +519,7 @@ auditTrailService, failureHandler, threadPool, anonymousUser, getAuthorizationEn components.add(ipFilter.get()); DestructiveOperations destructiveOperations = new DestructiveOperations(settings, clusterService.getClusterSettings()); securityInterceptor.set(new SecurityServerTransportInterceptor(settings, threadPool, authcService.get(), - authzService, auditTrailService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, + authzService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, clusterService)); securityActionFilter.set(new SecurityActionFilter(authcService.get(), authzService, auditTrailService, getLicenseState(), diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index e1b59c76afcf6..8291c9a489e4d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -156,7 +156,8 @@ it to the action without an associated user (not via REST or transport - this is (ignore, aVoid) -> { chain.proceed(task, action, request, ActionListener.delegateFailure(listener, (ignore2, response) -> { - auditTrailService.get().actionResponse(requestId, authc, action, request, response); + auditTrailService.get().coordinatingActionResponse(requestId, authc, action, request, + response); listener.onResponse(response); })); })); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java index 1e9f2d444ae34..2aaa22725e708 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrail.java @@ -48,10 +48,6 @@ void accessGranted(String requestId, Authentication authentication, String actio void accessDenied(String requestId, Authentication authentication, String action, TransportRequest transportRequest, AuthorizationInfo authorizationInfo); - // this is the only audit method that is called *after* the action executed, when the response is available - void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, - TransportResponse transportResponse); - void tamperedRequest(String requestId, RestRequest request); void tamperedRequest(String requestId, String action, TransportRequest transportRequest); @@ -86,4 +82,9 @@ void runAsDenied(String requestId, Authentication authentication, RestRequest re void explicitIndexAccessEvent(String requestId, AuditLevel eventType, Authentication authentication, String action, String indices, String requestName, TransportAddress remoteAddress, AuthorizationInfo authorizationInfo); + // this is the only audit method that is called *after* the action executed, when the response is available + // it is however *only called for coordinating actions*, which are the actions that a client invokes as opposed to + // the actions that a node invokes in order to service a client request + void coordinatingActionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, + TransportResponse transportResponse); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java index 125b4f61948ed..373d39eee7d4e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/AuditTrailService.java @@ -117,10 +117,6 @@ public void accessGranted(String requestId, Authentication authentication, Strin public void accessDenied(String requestId, Authentication authentication, String action, TransportRequest transportRequest, AuthorizationInfo authorizationInfo) {} - @Override - public void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, - TransportResponse transportResponse) {} - @Override public void tamperedRequest(String requestId, RestRequest request) {} @@ -152,6 +148,11 @@ public void runAsDenied(String requestId, Authentication authentication, RestReq public void explicitIndexAccessEvent(String requestId, AuditLevel eventType, Authentication authentication, String action, String indices, String requestName, TransportAddress remoteAddress, AuthorizationInfo authorizationInfo) {} + + @Override + public void coordinatingActionResponse(String requestId, Authentication authentication, String action, + TransportRequest transportRequest, + TransportResponse transportResponse) { } } private static class CompositeAuditTrail implements AuditTrail { @@ -260,10 +261,11 @@ public void accessDenied(String requestId, Authentication authentication, String } @Override - public void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, - TransportResponse transportResponse) { + public void coordinatingActionResponse(String requestId, Authentication authentication, String action, + TransportRequest transportRequest, + TransportResponse transportResponse) { for (AuditTrail auditTrail : auditTrails) { - auditTrail.actionResponse(requestId, authentication, action, transportRequest, transportResponse); + auditTrail.coordinatingActionResponse(requestId, authentication, action, transportRequest, transportResponse); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java index d3353060b30a1..e0035b6efeb7b 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java @@ -553,12 +553,6 @@ public void accessDenied(String requestId, Authentication authentication, String } } - @Override - public void actionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest, - TransportResponse transportResponse) { - // not implemented yet - } - @Override public void tamperedRequest(String requestId, RestRequest request) { if (events.contains(TAMPERED_REQUEST) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { @@ -738,6 +732,13 @@ public void runAsDenied(String requestId, Authentication authentication, RestReq } } + @Override + public void coordinatingActionResponse(String requestId, Authentication authentication, String action, + TransportRequest transportRequest, + TransportResponse transportResponse) { + // not implemented yet + } + private class LogEntryBuilder { private final StringMapMessage logEntry; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java index fa6d7bab4318f..c6fdc4338eb16 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java @@ -7,7 +7,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.DestructiveOperations; @@ -33,18 +32,14 @@ import org.elasticsearch.transport.TransportService.ContextRestoreResponseHandler; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.SecurityContext; -import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.transport.ProfileConfigurations; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; -import org.elasticsearch.xpack.security.audit.AuditTrailService; -import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationUtils; -import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -58,7 +53,6 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor private final AuthenticationService authcService; private final AuthorizationService authzService; - private final AuditTrailService auditTrailService; private final SSLService sslService; private final Map profileFilters; private final XPackLicenseState licenseState; @@ -72,7 +66,6 @@ public SecurityServerTransportInterceptor(Settings settings, ThreadPool threadPool, AuthenticationService authcService, AuthorizationService authzService, - AuditTrailService auditTrailService, XPackLicenseState licenseState, SSLService sslService, SecurityContext securityContext, @@ -82,7 +75,6 @@ public SecurityServerTransportInterceptor(Settings settings, this.threadPool = threadPool; this.authcService = authcService; this.authzService = authzService; - this.auditTrailService = auditTrailService; this.licenseState = licenseState; this.sslService = sslService; this.securityContext = securityContext; @@ -168,7 +160,7 @@ public TransportRequestHandler interceptHandler( boolean forceExecution, TransportRequestHandler actualHandler) { return new ProfileSecuredRequestHandler<>(logger, action, forceExecution, executor, actualHandler, profileFilters, - auditTrailService, securityContext, licenseState, threadPool); + licenseState, threadPool); } private Map initializeProfileFilters(DestructiveOperations destructiveOperations) { @@ -193,8 +185,6 @@ public static class ProfileSecuredRequestHandler imp private final String action; private final TransportRequestHandler handler; private final Map profileFilters; - private final AuditTrailService auditTrailService; - private final SecurityContext securityContext; private final XPackLicenseState licenseState; private final ThreadContext threadContext; private final String executorName; @@ -204,22 +194,19 @@ public static class ProfileSecuredRequestHandler imp ProfileSecuredRequestHandler(Logger logger, String action, boolean forceExecution, String executorName, TransportRequestHandler handler, Map profileFilters, - AuditTrailService auditTrailService, SecurityContext securityContext, XPackLicenseState licenseState, ThreadPool threadPool) { this.logger = logger; this.action = action; this.executorName = executorName; this.handler = handler; this.profileFilters = profileFilters; - this.auditTrailService = auditTrailService; - this.securityContext = securityContext; this.licenseState = licenseState; this.threadContext = threadPool.getThreadContext(); this.threadPool = threadPool; this.forceExecution = forceExecution; } - private AbstractRunnable getReceiveRunnable(T request, TransportChannel channel, Task task, boolean securityEnabled) { + AbstractRunnable getReceiveRunnable(T request, TransportChannel channel, Task task) { return new AbstractRunnable() { @Override public boolean isForceExecution() { @@ -238,49 +225,7 @@ public void onFailure(Exception e) { @Override protected void doRun() throws Exception { - if (securityEnabled) { - // extract the requestId and the authentication from the threadContext before executing the action - final String requestId = AuditUtil.extractRequestId(threadContext); - if (requestId == null) { - channel.sendResponse(new ElasticsearchSecurityException("requestId is unexpectedly missing")); - return; - } - final Authentication authentication = securityContext.getAuthentication(); - if (authentication == null) { - channel.sendResponse(new ElasticsearchSecurityException("authn is unexpectedly missing")); - return; - } - handler.messageReceived(request, new TransportChannel() { - - @Override - public String getProfileName() { - return channel.getProfileName(); - } - - @Override - public String getChannelType() { - return channel.getChannelType(); - } - - @Override - public Version getVersion() { - return channel.getVersion(); - } - - @Override - public void sendResponse(TransportResponse response) throws IOException { - auditTrailService.get().actionResponse(requestId, authentication, action, request, response); - channel.sendResponse(response); - } - - @Override - public void sendResponse(Exception exception) throws IOException { - channel.sendResponse(exception); - } - }, task); - } else { - handler.messageReceived(request, channel, task); - } + handler.messageReceived(request, channel, task); } }; } @@ -296,10 +241,9 @@ public String toString() { @Override public void messageReceived(T request, TransportChannel channel, Task task) throws Exception { - final boolean securityEnabled = licenseState.isSecurityEnabled(); - final AbstractRunnable receiveMessage = getReceiveRunnable(request, channel, task, securityEnabled); + final AbstractRunnable receiveMessage = getReceiveRunnable(request, channel, task); try (ThreadContext.StoredContext ctx = threadContext.newStoredContext(true)) { - if (securityEnabled) { + if (licenseState.isSecurityEnabled()) { String profile = channel.getProfileName(); ServerTransportFilter filter = profileFilters.get(profile); @@ -315,7 +259,7 @@ public void messageReceived(T request, TransportChannel channel, Task task) thro assert filter != null; final Thread executingThread = Thread.currentThread(); - CheckedConsumer consumer = (aVoid) -> { + CheckedConsumer consumer = (x) -> { final Executor executor; if (executingThread == Thread.currentThread()) { // only fork off if we get called on another thread this means we moved to diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java index 137fdba910e95..5b110e57f374a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/ServerTransportFilter.java @@ -14,7 +14,6 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.open.OpenIndexAction; import org.elasticsearch.action.support.DestructiveOperations; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.transport.TaskTransportChannel; @@ -29,7 +28,6 @@ import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.security.action.SecurityActionMapper; -import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; @@ -103,7 +101,6 @@ requests from all the nodes are attached with a user (either a serialize final Version version = transportChannel.getVersion(); authcService.authenticate(securityAction, request, true, ActionListener.wrap((authentication) -> { if (authentication != null) { - assert Strings.hasText(AuditUtil.extractRequestId(threadContext)); if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) && SystemUser.is(authentication.getUser()) == false) { securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index a817931dbcfbc..7ace2ab59bb70 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -117,7 +117,7 @@ public void testApply() throws Exception { mockChain(task, "_action", request, actionResponse); filter.apply(task, "_action", request, listener, chain); verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - verify(auditTrail).actionResponse(eq(requestId), eq(authentication), eq("_action"), eq(request), eq(actionResponse)); + verify(auditTrail).coordinatingActionResponse(eq(requestId), eq(authentication), eq("_action"), eq(request), eq(actionResponse)); } public void testApplyRestoresThreadContext() throws Exception { @@ -139,7 +139,7 @@ public void testApplyRestoresThreadContext() throws Exception { assertNull(threadContext.getTransient(AuthenticationField.AUTHENTICATION_KEY)); assertNull(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - verify(auditTrail).actionResponse(eq(requestId), eq(authentication), eq("_action"), eq(request), eq(actionResponse)); + verify(auditTrail).coordinatingActionResponse(eq(requestId), eq(authentication), eq("_action"), eq(request), eq(actionResponse)); } public void testApplyAsSystemUser() throws Exception { @@ -235,8 +235,7 @@ public void testApplyDestructiveOperations() throws Exception { } else { verify(authzService).authorize(eq(authentication), eq(action), eq(request), any(ActionListener.class)); verify(chain).proceed(eq(task), eq(action), eq(request), any(ActionListener.class)); - verify(auditTrail).actionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq(action), eq(request), - eq(actionResponse)); + verify(auditTrail).coordinatingActionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq(action), eq(request), eq(actionResponse)); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 7a27bb19cc6d0..d1c19bd7a7e66 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.authz; -import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; @@ -170,7 +169,6 @@ import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Predicate; @@ -282,8 +280,7 @@ auditTrailService, new DefaultAuthenticationFailureHandler(Collections.emptyMap( operatorPrivilegesService); } - private void authorize(Authentication authentication, String action, TransportRequest request, - BiConsumer postAuthz) { + private void authorize(Authentication authentication, String action, TransportRequest request) { PlainActionFuture done = new PlainActionFuture<>(); PlainActionFuture indicesPermissions = new PlainActionFuture<>(); PlainActionFuture originatingAction = new PlainActionFuture<>(); @@ -307,12 +304,6 @@ private void authorize(Authentication authentication, String action, TransportRe authorizationInfoHeader = mock(AuthorizationInfo.class); threadContext.putTransient(AUTHORIZATION_INFO_KEY, authorizationInfoHeader); } - boolean generateRequestIdBeforeAuthz = (false == User.isInternal(authentication.getUser())) || - randomBoolean(); - SetOnce requestId = new SetOnce<>(); - if (generateRequestIdBeforeAuthz) { - requestId.set(AuditUtil.getOrGenerateRequestId(threadContext)); - } Mockito.reset(operatorPrivilegesService); final AtomicBoolean operatorPrivilegesChecked = new AtomicBoolean(false); final ElasticsearchSecurityException operatorPrivilegesException = @@ -329,35 +320,16 @@ private void authorize(Authentication authentication, String action, TransportRe originatingAction.onResponse(threadContext.getTransient(ORIGINATING_ACTION_KEY)); authorizationInfo.onResponse(threadContext.getTransient(AUTHORIZATION_INFO_KEY)); indicesPermissions.onResponse(threadContext.getTransient(INDICES_PERMISSIONS_KEY)); - if (generateRequestIdBeforeAuthz) { - assertThat(AuditUtil.extractRequestId(threadContext), is(requestId.get())); - } else { - requestId.set(AuditUtil.extractRequestId(threadContext)); - } done.onResponse(threadContext.getTransient(someRandomHeader)); assertNull(verify(operatorPrivilegesService).check(action, request, threadContext)); }, e -> { - if (generateRequestIdBeforeAuthz) { - assertThat(AuditUtil.extractRequestId(threadContext), is(requestId.get())); - } else { - requestId.set(AuditUtil.extractRequestId(threadContext)); - } if (shouldFailOperatorPrivilegesCheck && operatorPrivilegesChecked.get()) { assertSame(operatorPrivilegesException, e.getCause()); } done.onFailure(e); }); authorizationService.authorize(authentication, action, request, listener); - Object someRandomHeaderValueInListener; - try { - someRandomHeaderValueInListener = done.actionGet(); - } catch (Exception e) { - postAuthz.accept(null, requestId.get()); - throw e; - } - if (generateRequestIdBeforeAuthz) { - assertThat(AuditUtil.extractRequestId(threadContext), is(requestId.get())); - } + Object someRandomHeaderValueInListener = done.actionGet(); assertThat(someRandomHeaderValueInListener, sameInstance(someRandomHeaderValue)); assertThat(threadContext.getTransient(someRandomHeader), sameInstance(someRandomHeaderValue)); // authorization restores any previously existing transient headers @@ -383,16 +355,15 @@ private void authorize(Authentication authentication, String action, TransportRe if (authorizationInfoHeader != null) { assertThat(authorizationInfo.actionGet(), not(sameInstance(authorizationInfoHeader))); } - assertThat(authorizationInfo.actionGet(), instanceOf(AuthorizationInfo.class)); // except originating action, which is not overwritten if (originatingActionHeader != null) { assertThat(originatingAction.actionGet(), sameInstance(originatingActionHeader)); } - postAuthz.accept((AuthorizationInfo) authorizationInfo.actionGet(), requestId.get()); } public void testActionsForSystemUserIsAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); // A failure would throw an exception final Authentication authentication = createAuthentication(SystemUser.INSTANCE); @@ -411,12 +382,9 @@ public void testActionsForSystemUserIsAuthorized() throws IOException { "indices:admin/seq_no/renew_retention_lease", "indices:admin/settings/update" }; for (String action : actions) { - authorize(authentication, action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{SystemUser.ROLE_NAME})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); - - }); + authorize(authentication, action, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { SystemUser.ROLE_NAME })); } verifyNoMoreInteractions(auditTrail); @@ -425,33 +393,36 @@ public void testActionsForSystemUserIsAuthorized() throws IOException { public void testIndicesActionsForSystemUserWhichAreNotAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = createAuthentication(SystemUser.INSTANCE); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:"), eq(request), - authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); - }), "indices:", SystemUser.INSTANCE.principal()); + () -> authorize(authentication, "indices:", request), + "indices:", SystemUser.INSTANCE.principal()); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:"), eq(request), + authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); verifyNoMoreInteractions(auditTrail); } public void testClusterAdminActionsForSystemUserWhichAreNotAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = createAuthentication(SystemUser.INSTANCE); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "cluster:admin/whatever", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/whatever"), eq(request), - authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); - }), "cluster:admin/whatever", SystemUser.INSTANCE.principal()); + () -> authorize(authentication, "cluster:admin/whatever", request), + "cluster:admin/whatever", SystemUser.INSTANCE.principal()); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/whatever"), eq(request), + authzInfoRoles(new String[] { SystemUser.ROLE_NAME })); verifyNoMoreInteractions(auditTrail); } public void testClusterAdminSnapshotStatusActionForSystemUserWhichIsNotAuthorized() throws IOException { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = createAuthentication(SystemUser.INSTANCE); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "cluster:admin/snapshot/status", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/snapshot/status"), eq(request), - authzInfoRoles(new String[]{SystemUser.ROLE_NAME})); - }), "cluster:admin/snapshot/status", SystemUser.INSTANCE.principal()); + () -> authorize(authentication, "cluster:admin/snapshot/status", request), + "cluster:admin/snapshot/status", SystemUser.INSTANCE.principal()); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/snapshot/status"), eq(request), + authzInfoRoles(new String[] { SystemUser.ROLE_NAME })); verifyNoMoreInteractions(auditTrail); } @@ -471,14 +442,13 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build final ConfigurableClusterPrivilege[] configurableClusterPrivileges = new ConfigurableClusterPrivilege[] { configurableClusterPrivilege }; + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); RoleDescriptor role = new RoleDescriptor("role1", null, null, null, configurableClusterPrivileges, null, null ,null); roleMap.put("role1", role); - authorize(authentication, DeletePrivilegesAction.NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(DeletePrivilegesAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }); + authorize(authentication, DeletePrivilegesAction.NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(DeletePrivilegesAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -498,14 +468,15 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build final ConfigurableClusterPrivilege[] configurableClusterPrivileges = new ConfigurableClusterPrivilege[] { configurableClusterPrivilege }; + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); RoleDescriptor role = new RoleDescriptor("role1", null, null, null, configurableClusterPrivileges, null, null ,null); roleMap.put("role1", role); assertThrowsAuthorizationException( - () -> authorize(authentication, DeletePrivilegesAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("cluster:admin/xpack/security/privilege/delete"), - eq(request), authzInfoRoles(new String[]{role.getName()})); - }), DeletePrivilegesAction.NAME, "user1"); + () -> authorize(authentication, DeletePrivilegesAction.NAME, request), + DeletePrivilegesAction.NAME, "user1"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(DeletePrivilegesAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -513,11 +484,12 @@ public void testNoRolesCausesDenial() throws IOException { final TransportRequest request = new SearchRequest(); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), - eq(request), authzInfoRoles(Role.EMPTY.names())); - }), "indices:a", "test user"); + () -> authorize(authentication, "indices:a", request), + "indices:a", "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } @@ -526,11 +498,10 @@ public void testUserWithNoRolesCanPerformRemoteSearch() throws IOException { request.indices("other_cluster:index1", "*_cluster:index2", "other_cluster:other_*"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search"), eq(request), - authzInfoRoles(Role.EMPTY.names())); - }); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); + authorize(authentication, SearchAction.NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } @@ -540,21 +511,21 @@ public void testUserWithNoRolesPerformsRemoteSearchWithScroll() { when(searchScrollRequest.parseScrollId()).thenReturn(parsedScrollId); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); for (final boolean hasLocalIndices: List.of(true, false)) { when(parsedScrollId.hasLocalIndices()).thenReturn(hasLocalIndices); if (hasLocalIndices) { assertThrowsAuthorizationException( - () -> authorize(authentication, SearchScrollAction.NAME, searchScrollRequest, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/scroll"), - eq(searchScrollRequest), authzInfoRoles(Role.EMPTY.names())); - }), "indices:data/read/scroll", "test user" + () -> authorize(authentication, SearchScrollAction.NAME, searchScrollRequest), + "indices:data/read/scroll", "test user" ); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), + eq("indices:data/read/scroll"), eq(searchScrollRequest), + authzInfoRoles(Role.EMPTY.names())); } else { - authorize(authentication, SearchScrollAction.NAME, searchScrollRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/scroll"), - eq(searchScrollRequest), authzInfoRoles(Role.EMPTY.names())); - }); + authorize(authentication, SearchScrollAction.NAME, searchScrollRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchScrollAction.NAME), eq(searchScrollRequest), + authzInfoRoles(Role.EMPTY.names())); } verifyNoMoreInteractions(auditTrail); } @@ -570,11 +541,12 @@ public void testUserWithNoRolesCannotPerformLocalSearch() throws IOException { request.indices("no_such_cluster:index"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), - eq(request), authzInfoRoles(Role.EMPTY.names())); - }), SearchAction.NAME, "test user"); + () -> authorize(authentication, SearchAction.NAME, request), + SearchAction.NAME, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } @@ -587,11 +559,12 @@ public void testUserWithNoRolesCanPerformMultiClusterSearch() throws IOException request.indices("local_index", "wildcard_*", "other_cluster:remote_index", "*:foo?"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), - eq(request), authzInfoRoles(Role.EMPTY.names())); - }), SearchAction.NAME, "test user"); + () -> authorize(authentication, SearchAction.NAME, request), + SearchAction.NAME, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } @@ -599,11 +572,12 @@ public void testUserWithNoRolesCannotSql() throws IOException { TransportRequest request = new SqlQueryRequest(); Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, SqlQueryAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/sql"), - eq(request), authzInfoRoles(Role.EMPTY.names())); - }), SqlQueryAction.NAME, "test user"); + () -> authorize(authentication, SqlQueryAction.NAME, request), + SqlQueryAction.NAME, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SqlQueryAction.NAME), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } @@ -616,17 +590,19 @@ public void testRemoteIndicesOnlyWorkWithApplicableRequestTypes() throws IOExcep request.indices("other_cluster:index1", "other_cluster:index2"); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, DeleteIndexAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:admin/delete"), - eq(request), authzInfoRoles(Role.EMPTY.names())); - }), DeleteIndexAction.NAME, "test user"); + () -> authorize(authentication, DeleteIndexAction.NAME, request), + DeleteIndexAction.NAME, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(DeleteIndexAction.NAME), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } public void testUserWithNoRolesOpenPointInTimeWithRemoteIndices() { final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); for (final boolean hasLocalIndices: List.of(true, false)) { final String[] indices = new String[] { hasLocalIndices ? @@ -640,17 +616,17 @@ public void testUserWithNoRolesOpenPointInTimeWithRemoteIndices() { ); if (hasLocalIndices) { assertThrowsAuthorizationException( - () -> authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/open_point_in_time"), - eq(openPointInTimeRequest), authzInfoRoles(Role.EMPTY.names())); - }), "indices:data/read/open_point_in_time", "test user" + () -> authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest), + "indices:data/read/open_point_in_time", "test user" ); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), + eq("indices:data/read/open_point_in_time"), eq(openPointInTimeRequest), + authzInfoRoles(Role.EMPTY.names())); } else { - authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/open_point_in_time"), - eq(openPointInTimeRequest), authzInfoRoles(Role.EMPTY.names())); - }); + authorize(authentication, OpenPointInTimeAction.NAME, openPointInTimeRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), + eq("indices:data/read/open_point_in_time"), eq(openPointInTimeRequest), + authzInfoRoles(Role.EMPTY.names())); } verifyNoMoreInteractions(auditTrail); } @@ -660,11 +636,11 @@ public void testUserWithNoRolesCanClosePointInTime() { final ClosePointInTimeRequest closePointInTimeRequest = new ClosePointInTimeRequest(randomAlphaOfLength(8)); final Authentication authentication = createAuthentication(new User("test user")); mockEmptyMetadata(); - authorize(authentication, ClosePointInTimeAction.NAME, closePointInTimeRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(Role.EMPTY.names())); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/close_point_in_time"), - eq(closePointInTimeRequest), authzInfoRoles(Role.EMPTY.names())); - }); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); + authorize(authentication, ClosePointInTimeAction.NAME, closePointInTimeRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), + eq("indices:data/read/close_point_in_time"), eq(closePointInTimeRequest), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } @@ -675,27 +651,28 @@ public void testUnknownRoleCausesDenial() throws IOException { String action = tuple.v1(); TransportRequest request = tuple.v2(); final Authentication authentication = createAuthentication(new User("test user", "non-existent-role")); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), - eq(request), authzInfoRoles(Role.EMPTY.names())); - }), action, "test user"); + () -> authorize(authentication, action, request), + action, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } public void testThatNonIndicesAndNonClusterActionIsDenied() throws IOException { final TransportRequest request = mock(TransportRequest.class); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("test user", "a_all")); final RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); roleMap.put("a_all", role); assertThrowsAuthorizationException( - () -> authorize(authentication, "whatever", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("whatever"), - eq(request), authzInfoRoles(new String[]{role.getName()})); - }), "whatever", "test user"); + () -> authorize(authentication, "whatever", request), + "whatever", "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("whatever"), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -706,28 +683,28 @@ public void testThatRoleWithNoIndicesIsDenied() throws IOException { new Tuple<>(SqlQueryAction.NAME, new SqlQueryRequest())); String action = tuple.v1(); TransportRequest request = tuple.v2(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("test user", "no_indices")); RoleDescriptor role = new RoleDescriptor("no_indices", null, null, null); roleMap.put("no_indices", role); mockEmptyMetadata(); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), - eq(request), authzInfoRoles(new String[]{role.getName()})); - }), action, "test user"); + () -> authorize(authentication, action, request), + action, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } public void testElasticUserAuthorizedForNonChangePasswordRequestsWhenNotInSetupMode() throws IOException { final Authentication authentication = createAuthentication(new ElasticUser(true)); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Tuple request = randomCompositeRequest(); - authorize(authentication, request.v1(), request.v2(), (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {ElasticUser.ROLE_NAME})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(request.v1()), - eq(request.v2()), authzInfoRoles(new String[] {ElasticUser.ROLE_NAME})); - }); - verifyNoMoreInteractions(auditTrail); + authorize(authentication, request.v1(), request.v2()); + + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(request.v1()), eq(request.v2()), + authzInfoRoles(new String[]{ElasticUser.ROLE_NAME})); } public void testSearchAgainstEmptyCluster() throws Exception { @@ -745,10 +722,10 @@ public void testSearchAgainstEmptyCluster() throws Exception { true, false)); assertThrowsAuthorizationException( - () -> authorize(authentication, SearchAction.NAME, searchRequest, (authorizationInfo, ignored) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), - eq(searchRequest), authzInfoRoles(new String[]{role.getName()})); - }), SearchAction.NAME, "test user"); + () -> authorize(authentication, SearchAction.NAME, searchRequest), + SearchAction.NAME, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(SearchAction.NAME), eq(searchRequest), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -781,55 +758,42 @@ public void testScrollRelatedRequestsAllowed() { final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); mockEmptyMetadata(); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); - authorize(authentication, ClearScrollAction.NAME, clearScrollRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/scroll/clear"), - eq(clearScrollRequest), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, ClearScrollAction.NAME, clearScrollRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClearScrollAction.NAME), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); final ParsedScrollId parsedScrollId = mock(ParsedScrollId.class); when(parsedScrollId.hasLocalIndices()).thenReturn(true); final SearchScrollRequest searchScrollRequest = mock(SearchScrollRequest.class); when(searchScrollRequest.parseScrollId()).thenReturn(parsedScrollId); - authorize(authentication, SearchScrollAction.NAME, searchScrollRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/scroll"), - eq(searchScrollRequest), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, SearchScrollAction.NAME, searchScrollRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchScrollAction.NAME), eq(searchScrollRequest), + authzInfoRoles(new String[]{role.getName()})); // We have to use a mock request for other Scroll actions as the actual requests are package private to SearchTransportService final TransportRequest request = mock(TransportRequest.class); - authorize(authentication, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[clear_scroll_contexts]"), - eq(request), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME), + eq(request), authzInfoRoles(new String[]{role.getName()})); - authorize(authentication, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[phase/fetch/id/scroll]"), - eq(request), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME), + eq(request), authzInfoRoles(new String[]{role.getName()})); - authorize(authentication, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[phase/query+fetch/scroll]"), - eq(request), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.QUERY_FETCH_SCROLL_ACTION_NAME), + eq(request), authzInfoRoles(new String[]{role.getName()})); - authorize(authentication, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[phase/query/scroll]"), - eq(request), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, SearchTransportService.QUERY_SCROLL_ACTION_NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.QUERY_SCROLL_ACTION_NAME), + eq(request), authzInfoRoles(new String[]{role.getName()})); - authorize(authentication, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/read/search[free_context/scroll]"), - eq(request), authzInfoRoles(new String[] {role.getName()})); - }); + authorize(authentication, SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(SearchTransportService.FREE_CONTEXT_SCROLL_ACTION_NAME), + eq(request), authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -840,12 +804,13 @@ public void testAuthorizeIndicesFailures() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), - eq(request), authzInfoRoles(new String[]{role.getName()})); - }), "indices:a", "test user"); + () -> authorize(authentication, "indices:a", request), + "indices:a", "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService, times(1)).state(); verify(state, times(1)).metadata(); @@ -859,14 +824,15 @@ public void testCreateIndexWithAliasWithoutPermissions() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, CreateIndexAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(IndicesAliasesAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }), IndicesAliasesAction.NAME, "test user"); + () -> authorize(authentication, CreateIndexAction.NAME, request), + IndicesAliasesAction.NAME, "test user"); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(IndicesAliasesAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService).state(); verify(state, times(1)).metadata(); @@ -880,14 +846,14 @@ public void testCreateIndexWithAlias() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a", "a2").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, CreateIndexAction.NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[] {role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:admin/aliases"), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }); + authorize(authentication, CreateIndexAction.NAME, request); + + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(CreateIndexAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:admin/aliases"), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService).state(); verify(state, times(1)).metadata(); @@ -908,11 +874,7 @@ public void testDenialErrorMessagesForSearchAction() throws IOException { TransportRequest request = new SearchRequest("all-1", "read-2", "write-3", "other-4"); ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(authentication, SearchAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:data/read/search"), - eq(request), authzInfoRoles(new String[]{role.getName()})); - })); - verifyNoMoreInteractions(auditTrail); + () -> authorize(authentication, SearchAction.NAME, request)); assertThat(securityException, throwableWithMessage( containsString("[" + SearchAction.NAME + "] is unauthorized for user [" + user.principal() + "] on indices ["))); assertThat(securityException, throwableWithMessage(containsString("write-3"))); @@ -944,11 +906,7 @@ public void testDenialErrorMessagesForBulkIngest() throws Exception { new BulkItemRequest(2, new DeleteRequest(index, "doc-3")) }); - authorize(authentication, TransportShardBulkAction.ACTION_NAME, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:data/write/bulk[s]"), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }); + authorize(authentication, TransportShardBulkAction.ACTION_NAME, request); MappingUpdatePerformer mappingUpdater = (m, s, l) -> l.onResponse(null); Consumer> waitForMappingUpdate = l -> l.onResponse(null); @@ -983,13 +941,14 @@ public void testDenialForAnonymousUser() throws IOException { RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(anonymousUser); assertThrowsAuthorizationException( - () -> authorize(authentication, "indices:a", request,(authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }), "indices:a", anonymousUser.principal()); + () -> authorize(authentication, "indices:a", request), + "indices:a", anonymousUser.principal()); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService, times(1)).state(); verify(state, times(1)).metadata(); @@ -1011,13 +970,13 @@ public void testDenialForAnonymousUserAuthorizationExceptionDisabled() throws IO RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); - })); + () -> authorize(authentication, "indices:a", request)); assertAuthenticationException(securityException, containsString("action [indices:a] requires authentication")); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService, times(1)).state(); verify(state, times(1)).metadata(); @@ -1031,15 +990,15 @@ public void testAuditTrailIsRecordedWhenIndexWildcardThrowsError() throws IOExce new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final IndexNotFoundException nfe = expectThrows( IndexNotFoundException.class, - () -> authorize(authentication, GetIndexAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(GetIndexAction.NAME), eq(request), - authzInfoRoles(new String[]{role.getName()})); - })); + () -> authorize(authentication, GetIndexAction.NAME, request)); assertThat(nfe.getIndex(), is(notNullValue())); assertThat(nfe.getIndex().getName(), is("not-an-index-*")); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(GetIndexAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); verify(clusterService).state(); verify(state, times(1)).metadata(); @@ -1047,17 +1006,19 @@ public void testAuditTrailIsRecordedWhenIndexWildcardThrowsError() throws IOExce public void testRunAsRequestWithNoRolesUser() throws IOException { final TransportRequest request = mock(TransportRequest.class); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("run as me", null, new User("test user", "admin"))); assertNotEquals(authentication.getUser().authenticatedUser(), authentication); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(Role.EMPTY.names())); - }), "indices:a", "test user", "run as me"); // run as [run as me] + () -> authorize(authentication, "indices:a", request), + "indices:a", "test user", "run as me"); // run as [run as me] + verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(Role.EMPTY.names())); verifyNoMoreInteractions(auditTrail); } public void testRunAsRequestWithoutLookedUpBy() throws IOException { + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); AuthenticateRequest request = new AuthenticateRequest("run as me"); roleMap.put("superuser", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); User user = new User("run as me", Strings.EMPTY_ARRAY, new User("test user", new String[]{"superuser"})); @@ -1065,10 +1026,10 @@ public void testRunAsRequestWithoutLookedUpBy() throws IOException { authentication.writeToContext(threadContext); assertNotEquals(user.authenticatedUser(), user); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, AuthenticateAction.NAME, request, (authorizationInfo, requestId) -> { - verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq(AuthenticateAction.NAME), eq(request), - authzInfoRoles(new String[] { ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName() })); - }), AuthenticateAction.NAME, "test user", "run as me"); // run as [run as me] + () -> authorize(authentication, AuthenticateAction.NAME, request), + AuthenticateAction.NAME, "test user", "run as me"); // run as [run as me] + verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq(AuthenticateAction.NAME), eq(request), + authzInfoRoles(new String[] { ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName() })); verifyNoMoreInteractions(auditTrail); } @@ -1081,12 +1042,13 @@ public void testRunAsRequestRunningAsUnAllowedUser() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, new String[]{"not the right user"}); roleMap.put("can run as", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }), "indices:a", "test user", "run as me"); + () -> authorize(authentication, "indices:a", request), + "indices:a", "test user", "run as me"); + verify(auditTrail).runAsDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -1116,19 +1078,20 @@ public void testRunAsRequestWithRunAsUserWithoutPermission() throws IOException } else { mockEmptyMetadata(); } + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationExceptionRunAs( - () -> authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{runAsRole.getName()})); - if (indexExists) { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{bRole.getName()})); - } else { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(Role.EMPTY.names())); - } - }), "indices:a", "test user", "run as me"); + () -> authorize(authentication, "indices:a", request), + "indices:a", "test user", "run as me"); + verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{runAsRole.getName()})); + if (indexExists) { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{bRole.getName()})); + } else { + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(Role.EMPTY.names())); + } verifyNoMoreInteractions(auditTrail); } @@ -1152,15 +1115,13 @@ public void testRunAsRequestWithValidPermissions() throws IOException { RoleDescriptor bRole = new RoleDescriptor("b", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("b").privileges("all").build()}, null); roleMap.put("b", bRole); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, "indices:a", request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{bRole.getName()})); - verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{runAsRole.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), - authzInfoRoles(new String[]{bRole.getName()})); - - }); + authorize(authentication, "indices:a", request); + verify(auditTrail).runAsGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{runAsRole.getName()})); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq("indices:a"), eq(request), + authzInfoRoles(new String[]{bRole.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -1179,6 +1140,7 @@ public void testGrantAllRestrictedUserCannotExecuteOperationAgainstSecurityIndic .numberOfReplicas(0) .build(),true) .build()); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); List> requests = new ArrayList<>(); requests.add(new Tuple<>(BulkAction.NAME + "[s]", @@ -1211,34 +1173,28 @@ public void testGrantAllRestrictedUserCannotExecuteOperationAgainstSecurityIndic String action = requestTuple.v1(); TransportRequest request = requestTuple.v2(); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }), action, "all_access_user"); + () -> authorize(authentication, action, request), + action, "all_access_user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } // we should allow waiting for the health of the index or any index if the user has this permission - ClusterHealthRequest clusterHealthRequest1 = new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, - INTERNAL_SECURITY_MAIN_INDEX_7)); - authorize(authentication, ClusterHealthAction.NAME, clusterHealthRequest1, (authorizationInfo, requestId) -> { - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(clusterHealthRequest1), - authzInfoRoles(new String[]{role.getName()})); - }); + ClusterHealthRequest request = new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)); + authorize(authentication, ClusterHealthAction.NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); // multiple indices - ClusterHealthRequest clusterHealthRequest2 = new ClusterHealthRequest(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7, - "foo", "bar"); - authorize(authentication, ClusterHealthAction.NAME, clusterHealthRequest2, (authorizationInfo, requestId) -> { - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(clusterHealthRequest2), - authzInfoRoles(new String[]{role.getName()})); - }); + request = new ClusterHealthRequest(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7, "foo", "bar"); + authorize(authentication, ClusterHealthAction.NAME, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); final SearchRequest searchRequest = new SearchRequest("_all"); - authorize(authentication, SearchAction.NAME, searchRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - }); + authorize(authentication, SearchAction.NAME, searchRequest); assertEquals(2, searchRequest.indices().length); assertEquals(IndicesAndAliasesResolver.NO_INDICES_OR_ALIASES_LIST, Arrays.asList(searchRequest.indices())); } @@ -1272,21 +1228,19 @@ public void testMonitoringOperationsAgainstSecurityIndexRequireAllowRestricted() final String action = requestTuple.v1(); final TransportRequest request = requestTuple.v2(); try (StoredContext ignore = threadContext.stashContext()) { + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication restrictedUserAuthn = createAuthentication(new User("restricted_user", "restricted_monitor")); - assertThrowsAuthorizationException(() -> authorize(restrictedUserAuthn, action, request, - ((authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(restrictedUserAuthn), eq(action), eq(request), - authzInfoRoles(new String[] { "restricted_monitor" })); - })), action, "restricted_user"); + assertThrowsAuthorizationException(() -> authorize(restrictedUserAuthn, action, request), action, "restricted_user"); + verify(auditTrail).accessDenied(eq(requestId), eq(restrictedUserAuthn), eq(action), eq(request), + authzInfoRoles(new String[] { "restricted_monitor" })); verifyNoMoreInteractions(auditTrail); } try (StoredContext ignore = threadContext.stashContext()) { + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final Authentication unrestrictedUserAuthn = createAuthentication(new User("unrestricted_user", "unrestricted_monitor")); - authorize(unrestrictedUserAuthn, action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{unrestrictedMonitorRole.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(unrestrictedUserAuthn), eq(action), eq(request), - authzInfoRoles(new String[] { "unrestricted_monitor" })); - }); + authorize(unrestrictedUserAuthn, action, request); + verify(auditTrail).accessGranted(eq(requestId), eq(unrestrictedUserAuthn), eq(action), eq(request), + authzInfoRoles(new String[] { "unrestricted_monitor" })); verifyNoMoreInteractions(auditTrail); } } @@ -1305,6 +1259,7 @@ public void testSuperusersCanExecuteOperationAgainstSecurityIndex() throws IOExc .numberOfReplicas(0) .build(), true) .build()); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); List> requests = new ArrayList<>(); requests.add( @@ -1336,12 +1291,9 @@ public void testSuperusersCanExecuteOperationAgainstSecurityIndex() throws IOExc final TransportRequest request = requestTuple.v2(); try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(false)) { final Authentication authentication = createAuthentication(superuser); - authorize(authentication, action, request, ((authorizationInfo, requestId) -> { - assertThat(authorizationInfo, - new RBACAuthorizationInfoRoleMatcher(new String[]{ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(superuser.roles())); - })); + authorize(authentication, action, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(superuser.roles())); } } } @@ -1360,14 +1312,12 @@ public void testSuperusersCanExecuteOperationAgainstSecurityIndexWithWildcard() .numberOfReplicas(0) .build(), true) .build()); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); String action = SearchAction.NAME; SearchRequest request = new SearchRequest("_all"); - authorize(authentication, action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, - new RBACAuthorizationInfoRoleMatcher(new String[]{ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(superuser.roles())); - }); + authorize(authentication, action, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(superuser.roles())); assertThat(request.indices(), arrayContainingInAnyOrder(INTERNAL_SECURITY_MAIN_INDEX_7, SECURITY_MAIN_ALIAS)); } @@ -1379,12 +1329,12 @@ public void testCompositeActionsAreImmediatelyRejected() { final Authentication authentication = createAuthentication(new User("test user", "no_indices")); final RoleDescriptor role = new RoleDescriptor("no_indices", null, null, null); roleMap.put("no_indices", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, action, request, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { role.getName() })); - }), action, "test user"); + () -> authorize(authentication, action, request), action, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { role.getName() })); verifyNoMoreInteractions(auditTrail); } @@ -1398,25 +1348,24 @@ public void testCompositeActionsIndicesAreNotChecked() throws IOException { new IndicesPrivileges[]{IndicesPrivileges.builder().indices(randomBoolean() ? "a" : "index").privileges("all").build()}, null); roleMap.put("role", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); - authorize(authentication, action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, - new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { role.getName() })); - }); + authorize(authentication, action, request); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { role.getName() })); verifyNoMoreInteractions(auditTrail); } public void testCompositeActionsMustImplementCompositeIndicesRequest() throws IOException { String action = randomCompositeRequest().v1(); TransportRequest request = mock(TransportRequest.class); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); User user = new User("test user", "role"); roleMap.put("role", new RoleDescriptor("role", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices(randomBoolean() ? "a" : "index").privileges("all").build()}, null)); IllegalStateException illegalStateException = expectThrows(IllegalStateException.class, - () -> authorize(createAuthentication(user), action, request, (authorizationInfo, requestId) -> {})); + () -> authorize(createAuthentication(user), action, request)); assertThat(illegalStateException.getMessage(), containsString("Composite and bulk actions must implement CompositeIndicesRequest")); } @@ -1460,13 +1409,10 @@ public void testCompositeActionsIndicesAreCheckedAtTheShardLevel() throws IOExce AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(false)) { - authorize(createAuthentication(userAllowed), action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, - new RBACAuthorizationInfoRoleMatcher(new String[]{"roleAllowed"})); - }); + authorize(createAuthentication(userAllowed), action, request); } assertThrowsAuthorizationException( - () -> authorize(createAuthentication(userDenied), action, request, (authorizationInfo, requestId) -> {}), action, "userDenied"); + () -> authorize(createAuthentication(userDenied), action, request), action, "userDenied"); } public void testAuthorizationOfIndividualBulkItems() throws IOException { @@ -1491,29 +1437,29 @@ public void testAuthorizationOfIndividualBulkItems() throws IOException { roleMap.put("my-role", role); mockEmptyMetadata(); - authorize(authentication, action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(DeleteAction.NAME), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(DeleteAction.NAME), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), - eq(DeleteAction.NAME), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[] { role.getName() })); // bulk request is allowed - }); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); + authorize(authentication, action, request); + + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(DeleteAction.NAME), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(DeleteAction.NAME), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), eq("concrete-index"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), + eq(DeleteAction.NAME), eq("alias-1"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), eq("alias-2"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[] { role.getName() })); // bulk request is allowed verifyNoMoreInteractions(auditTrail); } @@ -1534,22 +1480,21 @@ public void testAuthorizationOfIndividualBulkItemsWithDateMath() throws IOExcept final RoleDescriptor role = new RoleDescriptor("my-role", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("datemath-*").privileges("index").build()}, null); roleMap.put("my-role", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); - authorize(authentication, action, request, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - // both deletes should fail - verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), - eq(DeleteAction.NAME), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), - eq(IndexAction.NAME + ":op_type/index"), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), - eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); - // bulk request is allowed - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), - authzInfoRoles(new String[]{role.getName()})); - }); - + authorize(authentication, action, request); + + // both deletes should fail + verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_DENIED), eq(authentication), + eq(DeleteAction.NAME), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + verify(auditTrail, times(2)).explicitIndexAccessEvent(eq(requestId), eq(AuditLevel.ACCESS_GRANTED), eq(authentication), + eq(IndexAction.NAME + ":op_type/index"), Matchers.startsWith("datemath-"), eq(BulkItemRequest.class.getSimpleName()), + eq(request.remoteAddress()), authzInfoRoles(new String[] { role.getName() })); + // bulk request is allowed + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -1607,11 +1552,10 @@ public void testProxyRequestFailsOnNonProxyAction() { TransportRequest request = TransportRequest.Empty.INSTANCE; DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, request); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); User user = new User("test user", "role"); ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(createAuthentication(user), "indices:some/action", transportRequest, (authorizationInfo, requestId) -> { - - })); + () -> authorize(createAuthentication(user), "indices:some/action", transportRequest)); assertThat(ese.getCause(), instanceOf(IllegalStateException.class)); IllegalStateException illegalStateException = (IllegalStateException) ese.getCause(); assertThat(illegalStateException.getMessage(), @@ -1622,11 +1566,9 @@ public void testProxyRequestFailsOnNonProxyAction() { public void testProxyRequestFailsOnNonProxyRequest() { TransportRequest request = TransportRequest.Empty.INSTANCE; User user = new User("test user", "role"); + AuditUtil.getOrGenerateRequestId(threadContext); ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, - () -> authorize(createAuthentication(user), TransportActionProxy.getProxyAction("indices:some/action"), request, - (authorizationInfo, requestId) -> { - - })); + () -> authorize(createAuthentication(user), TransportActionProxy.getProxyAction("indices:some/action"), request)); assertThat(ese.getCause(), instanceOf(IllegalStateException.class)); IllegalStateException illegalStateException = (IllegalStateException) ese.getCause(); assertThat(illegalStateException.getMessage(), @@ -1643,12 +1585,12 @@ public void testProxyRequestAuthenticationDenied() throws IOException { final Authentication authentication = createAuthentication(new User("test user", "no_indices")); final RoleDescriptor role = new RoleDescriptor("no_indices", null, null, null); roleMap.put("no_indices", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); assertThrowsAuthorizationException( - () -> authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(proxiedRequest), - authzInfoRoles(new String[]{role.getName()})); - }), action, "test user"); + () -> authorize(authentication, action, transportRequest), action, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(proxiedRequest), + authzInfoRoles(new String[]{role.getName()})); verifyNoMoreInteractions(auditTrail); } @@ -1657,6 +1599,7 @@ public void testProxyRequestAuthenticationGrantedWithAllPrivileges() { new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); final Authentication authentication = createAuthentication(new User("test user", "a_all")); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); @@ -1664,11 +1607,9 @@ public void testProxyRequestAuthenticationGrantedWithAllPrivileges() { final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); final TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, clearScrollRequest); final String action = TransportActionProxy.getProxyAction(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); - authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); - }); + authorize(authentication, action, transportRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); } public void testProxyRequestAuthenticationGranted() { @@ -1678,15 +1619,14 @@ public void testProxyRequestAuthenticationGranted() { roleMap.put("a_all", role); mockEmptyMetadata(); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); final ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); final TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, clearScrollRequest); final String action = TransportActionProxy.getProxyAction(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); - authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { - assertThat(authorizationInfo, new RBACAuthorizationInfoRoleMatcher(new String[]{role.getName()})); - verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); - }); + authorize(authentication, action, transportRequest); + verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); } public void testProxyRequestAuthenticationDeniedWithReadPrivileges() throws IOException { @@ -1694,16 +1634,16 @@ public void testProxyRequestAuthenticationDeniedWithReadPrivileges() throws IOEx final RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("read").build()}, null); roleMap.put("a_all", role); + final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); mockEmptyMetadata(); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); TransportRequest transportRequest = TransportActionProxy.wrapRequest(node, clearScrollRequest); String action = TransportActionProxy.getProxyAction(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); assertThrowsAuthorizationException( - () -> authorize(authentication, action, transportRequest, (authorizationInfo, requestId) -> { - verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), - authzInfoRoles(new String[]{role.getName()})); - }), action, "test user"); + () -> authorize(authentication, action, transportRequest), action, "test user"); + verify(auditTrail).accessDenied(eq(requestId), eq(authentication), eq(action), eq(clearScrollRequest), + authzInfoRoles(new String[]{role.getName()})); } public void testAuthorizationEngineSelection() { @@ -1837,8 +1777,8 @@ public void testOperatorPrivileges() { AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("user1", "role1")); assertThrowsAuthorizationException( - () -> authorize(authentication, "cluster:admin/whatever", mock(TransportRequest.class), mock(BiConsumer.class)), - "cluster:admin/whatever", "user1"); + () -> authorize(authentication, "cluster:admin/whatever", mock(TransportRequest.class)), + "cluster:admin/whatever", "user1"); // The operator related exception is verified in the authorize(...) call verifyZeroInteractions(auditTrail); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java index 0f2be0ff108bd..d2019af7e03e6 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java @@ -6,7 +6,6 @@ package org.elasticsearch.xpack.security.transport; import org.elasticsearch.Version; -import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.main.MainAction; import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.cluster.ClusterState; @@ -17,19 +16,15 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.tasks.Task; import org.elasticsearch.test.ClusterServiceUtils; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.VersionUtils; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.Transport.Connection; -import org.elasticsearch.transport.TransportChannel; import org.elasticsearch.transport.TransportException; import org.elasticsearch.transport.TransportInterceptor.AsyncSender; import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.transport.TransportRequestHandler; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportResponse; import org.elasticsearch.transport.TransportResponse.Empty; @@ -38,14 +33,10 @@ import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef; -import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.SSLService; -import org.elasticsearch.xpack.security.audit.AuditTrail; -import org.elasticsearch.xpack.security.audit.AuditTrailService; -import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.After; @@ -55,12 +46,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import static org.hamcrest.Matchers.arrayWithSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -73,8 +62,6 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase { private Settings settings; private ThreadPool threadPool; private ThreadContext threadContext; - private AuditTrailService auditTrailService; - private AuditTrail auditTrail; private XPackLicenseState xPackLicenseState; private SecurityContext securityContext; private ClusterService clusterService; @@ -86,9 +73,6 @@ public void setUp() throws Exception { threadPool = new TestThreadPool(getTestName()); clusterService = ClusterServiceUtils.createClusterService(threadPool); threadContext = threadPool.getThreadContext(); - auditTrailService = mock(AuditTrailService.class); - auditTrail = mock(AuditTrail.class); - when(auditTrailService.get()).thenReturn(auditTrail); securityContext = spy(new SecurityContext(settings, threadPool.getThreadContext())); xPackLicenseState = mock(XPackLicenseState.class); when(xPackLicenseState.isSecurityEnabled()).thenReturn(true); @@ -102,8 +86,8 @@ public void stopThreadPool() throws Exception { public void testSendAsyncUserActionWhenUnlicensed() { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener when(xPackLicenseState.isSecurityEnabled()).thenReturn(false); @@ -130,8 +114,8 @@ public void sendRequest(Transport.Connection conne public void testSendAsyncInternalActionWhenUnlicensed() { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener when(xPackLicenseState.isSecurityEnabled()).thenReturn(false); @@ -159,8 +143,8 @@ public void sendRequest(Transport.Connection conne public void testSendAsyncWithStateNotRecovered() { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); final boolean authAllowed = randomBoolean(); when(xPackLicenseState.isSecurityEnabled()).thenReturn(authAllowed); @@ -198,8 +182,8 @@ public void testSendAsync() throws Exception { final Authentication authentication = new Authentication(user, new RealmRef("ldap", "foo", "node1"), null); authentication.writeToContext(threadContext); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -234,8 +218,8 @@ public void testSendAsyncSwitchToSystem() throws Exception { threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -265,8 +249,8 @@ public void sendRequest(Transport.Connection conne public void testSendWithoutUser() throws Exception { SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService) { @Override void assertNoAuthentication(String action) { @@ -301,8 +285,8 @@ public void testSendToNewerVersionSetsCorrectVersion() throws Exception { threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -342,8 +326,8 @@ public void testSendToOlderVersionSetsCorrectVersion() throws Exception { threadContext.putTransient(AuthorizationServiceField.ORIGINATING_ACTION_KEY, "indices:foo"); SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - mock(AuthenticationService.class), mock(AuthorizationService.class), auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, + mock(AuthenticationService.class), mock(AuthorizationService.class), xPackLicenseState, mock(SSLService.class), + securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); ClusterServiceUtils.setState(clusterService, clusterService.state()); // force state update to trigger listener @@ -439,67 +423,6 @@ public void handleException(TransportException exp) { assertEquals("value", threadContext.getHeader("key")); } - public void testAuditTransportResponse() throws Exception { - User user = new User(randomAlphaOfLength(4), generateRandomStringArray(2, 4, false)); - Authentication authentication = new Authentication(user, new RealmRef("test", "test", "foo"), null); - AuthenticationService authenticationService = mock(AuthenticationService.class); - TransportRequest request = mock(TransportRequest.class); - AtomicReference requestIdFromAuthn = new AtomicReference<>(); - doAnswer(i -> { - final Object[] args = i.getArguments(); - assertThat(args, arrayWithSize(4)); - ActionListener callback = (ActionListener) args[args.length - 1]; - requestIdFromAuthn.set(AuditUtil.getOrGenerateRequestId(threadContext)); - threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication); - threadContext.putHeader(AuthenticationField.AUTHENTICATION_KEY, authentication.encode()); - callback.onResponse(authentication); - return Void.TYPE; - }).when(authenticationService).authenticate(eq("_action"), eq(request), eq(true), any(ActionListener.class)); - AuthorizationService authorizationService = mock(AuthorizationService.class); - doAnswer((i) -> { - ActionListener callback = (ActionListener) i.getArguments()[3]; - callback.onResponse(null); - return Void.TYPE; - }).when(authorizationService) - .authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); - SecurityServerTransportInterceptor interceptor = new SecurityServerTransportInterceptor(settings, threadPool, - authenticationService, authorizationService, auditTrailService, xPackLicenseState, - mock(SSLService.class), securityContext, new DestructiveOperations(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, - Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))), clusterService); - when(xPackLicenseState.isSecurityEnabled()).thenReturn(true); - TransportRequestHandler transportRequestHandler = mock(TransportRequestHandler.class); - TransportRequestHandler wrappedTransportRequestHandler = interceptor.interceptHandler("_action", randomFrom("same", "generic", - "management"), randomBoolean(), transportRequestHandler); - TransportChannel transportChannel = mock(TransportChannel.class); - when(transportChannel.getProfileName()).thenReturn(randomFrom(TransportService.DIRECT_RESPONSE_PROFILE, "default")); - when(transportChannel.getChannelType()).thenReturn(randomAlphaOfLength(4)); - when(transportChannel.getVersion()).thenReturn(VersionUtils.randomVersion(random())); - Task task = mock(Task.class); - TransportResponse transportResponse = mock(TransportResponse.class); - // "send response" generates the audit message - doAnswer(i -> { - TransportChannel wrappedTransportChannel = (TransportChannel) i.getArguments()[1]; - assertThat(wrappedTransportChannel.getProfileName(), is(transportChannel.getProfileName())); - assertThat(wrappedTransportChannel.getChannelType(), is(transportChannel.getChannelType())); - assertThat(wrappedTransportChannel.getVersion(), is(transportChannel.getVersion())); - wrappedTransportChannel.sendResponse(transportResponse); - return Void.TYPE; - }).when(transportRequestHandler).messageReceived(eq(request), any(TransportChannel.class), eq(task)); - wrappedTransportRequestHandler.messageReceived(request, transportChannel, task); - verify(auditTrail).actionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq("_action"), eq(request), - eq(transportResponse)); - doAnswer(i -> { - TransportChannel wrappedTransportChannel = (TransportChannel) i.getArguments()[1]; - assertThat(wrappedTransportChannel.getProfileName(), is(transportChannel.getProfileName())); - assertThat(wrappedTransportChannel.getChannelType(), is(transportChannel.getChannelType())); - assertThat(wrappedTransportChannel.getVersion(), is(transportChannel.getVersion())); - wrappedTransportChannel.sendResponse(new RuntimeException()); - return Void.TYPE; - }).when(transportRequestHandler).messageReceived(eq(request), any(TransportChannel.class), eq(task)); - wrappedTransportRequestHandler.messageReceived(request, transportChannel, task); - verifyNoMoreInteractions(auditTrail); - } - private String[] randomRoles() { return generateRandomStringArray(3, 10, false, true); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java index 1c5f94fd903e0..f8608d94001a7 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java @@ -29,7 +29,6 @@ import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.security.user.XPackUser; -import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.junit.Before; @@ -58,8 +57,6 @@ public class ServerTransportFilterTests extends ESTestCase { private TransportChannel channel; private boolean failDestructiveOperations; private DestructiveOperations destructiveOperations; - private ThreadContext threadContext; - private ServerTransportFilter serverTransportFilter; @Before public void init() throws Exception { @@ -70,13 +67,9 @@ public void init() throws Exception { when(channel.getVersion()).thenReturn(Version.CURRENT); failDestructiveOperations = randomBoolean(); Settings settings = Settings.builder() - .put(DestructiveOperations.REQUIRES_NAME_SETTING.getKey(), failDestructiveOperations) - .put("path.home", createTempDir()).build(); + .put(DestructiveOperations.REQUIRES_NAME_SETTING.getKey(), failDestructiveOperations).build(); destructiveOperations = new DestructiveOperations(settings, new ClusterSettings(settings, Collections.singleton(DestructiveOperations.REQUIRES_NAME_SETTING))); - threadContext = new ThreadContext(settings); - serverTransportFilter = new ServerTransportFilter(authcService, authzService, threadContext, false, destructiveOperations, - new SecurityContext(settings, threadContext), new XPackLicenseState(settings, () -> 0)); } public void testInbound() throws Exception { @@ -87,13 +80,13 @@ public void testInbound() throws Exception { doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); - AuditUtil.generateRequestId(threadContext); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(true), any(ActionListener.class)); + ServerTransportFilter filter = getNodeFilter(); PlainActionFuture future = new PlainActionFuture<>(); - serverTransportFilter.inbound("_action", request, channel, future); + filter.inbound("_action", request, channel, future); //future.get(); // don't block it's not called really just mocked verify(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); } @@ -109,13 +102,13 @@ public void testInboundDestructiveOperations() throws Exception { doAnswer(i -> { final Object[] args = i.getArguments(); assertThat(args, arrayWithSize(4)); - AuditUtil.generateRequestId(threadContext); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq(action), eq(request), eq(true), any(ActionListener.class)); + ServerTransportFilter filter = getNodeFilter(); PlainActionFuture listener = mock(PlainActionFuture.class); - serverTransportFilter.inbound(action, request, channel, listener); + filter.inbound(action, request, channel, listener); if (failDestructiveOperations) { verify(listener).onFailure(isA(IllegalArgumentException.class)); verifyNoMoreInteractions(authzService); @@ -129,15 +122,15 @@ public void testInboundAuthenticationException() throws Exception { Exception authE = authenticationError("authc failed"); doAnswer(i -> { final Object[] args = i.getArguments(); - AuditUtil.generateRequestId(threadContext); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onFailure(authE); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(true), any(ActionListener.class)); + ServerTransportFilter filter = getNodeFilter(); try { PlainActionFuture future = new PlainActionFuture<>(); - serverTransportFilter.inbound("_action", request, channel, future); + filter.inbound("_action", request, channel, future); future.actionGet(); fail("expected filter inbound to throw an authentication exception on authentication error"); } catch (ElasticsearchSecurityException e) { @@ -147,11 +140,11 @@ public void testInboundAuthenticationException() throws Exception { } public void testInboundAuthorizationException() throws Exception { + ServerTransportFilter filter = getNodeFilter(); TransportRequest request = mock(TransportRequest.class); Authentication authentication = mock(Authentication.class); doAnswer(i -> { final Object[] args = i.getArguments(); - AuditUtil.generateRequestId(threadContext); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); @@ -163,7 +156,7 @@ public void testInboundAuthorizationException() throws Exception { doThrow(authorizationError("authz failed")) .when(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(ActionListener.class)); ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> { - serverTransportFilter.inbound("_action", request, channel, future); + filter.inbound("_action", request, channel, future); future.actionGet(); }); assertThat(e.getMessage(), equalTo("authz failed")); @@ -172,11 +165,11 @@ public void testInboundAuthorizationException() throws Exception { public void testAllowsNodeActions() throws Exception { final String internalAction = "internal:foo/bar"; final String nodeOrShardAction = "indices:action" + randomFrom("[s]", "[p]", "[r]", "[n]", "[s][p]", "[s][r]", "[f]"); + ServerTransportFilter filter = getNodeFilter(); TransportRequest request = mock(TransportRequest.class); Authentication authentication = new Authentication(new User("test", "superuser"), new RealmRef("test", "test", "node1"), null); doAnswer(i -> { final Object[] args = i.getArguments(); - AuditUtil.generateRequestId(threadContext); assertThat(args, arrayWithSize(4)); ActionListener callback = (ActionListener) args[args.length - 1]; callback.onResponse(authentication); @@ -190,13 +183,20 @@ public void testAllowsNodeActions() throws Exception { return Void.TYPE; }).when(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(true), any(ActionListener.class)); - serverTransportFilter.inbound(internalAction, request, channel, new PlainActionFuture<>()); + filter.inbound(internalAction, request, channel, new PlainActionFuture<>()); verify(authcService).authenticate(eq(internalAction), eq(request), eq(true), any(ActionListener.class)); verify(authzService).authorize(eq(authentication), eq(internalAction), eq(request), any(ActionListener.class)); - serverTransportFilter.inbound(nodeOrShardAction, request, channel, new PlainActionFuture<>()); + filter.inbound(nodeOrShardAction, request, channel, new PlainActionFuture<>()); verify(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(true), any(ActionListener.class)); verify(authzService).authorize(eq(authentication), eq(nodeOrShardAction), eq(request), any(ActionListener.class)); verifyNoMoreInteractions(authcService, authzService); } + + private ServerTransportFilter getNodeFilter() { + Settings settings = Settings.builder().put("path.home", createTempDir()).build(); + ThreadContext threadContext = new ThreadContext(settings); + return new ServerTransportFilter(authcService, authzService, threadContext, false, destructiveOperations, + new SecurityContext(settings, threadContext), new XPackLicenseState(settings, () -> 0)); + } } From cf59a6095a84d0d22732e6ad1437a3b11c03c8eb Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 15 Dec 2020 11:13:57 +0200 Subject: [PATCH 24/26] Checkstyle test --- .../security/action/filter/SecurityActionFilterTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index 7ace2ab59bb70..134101a1b2472 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -235,7 +235,8 @@ public void testApplyDestructiveOperations() throws Exception { } else { verify(authzService).authorize(eq(authentication), eq(action), eq(request), any(ActionListener.class)); verify(chain).proceed(eq(task), eq(action), eq(request), any(ActionListener.class)); - verify(auditTrail).coordinatingActionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq(action), eq(request), eq(actionResponse)); + verify(auditTrail).coordinatingActionResponse(eq(requestIdFromAuthn.get()), eq(authentication), eq(action), eq(request), + eq(actionResponse)); } } From 49a2852b85fa33da2d36446c6a6d0f859cb73d1a Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 15 Dec 2020 16:48:42 +0200 Subject: [PATCH 25/26] Nits --- .../main/java/org/elasticsearch/xpack/security/Security.java | 3 +-- .../xpack/security/authc/AuthenticationServiceTests.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 3002a51fd59fd..dee66ba170612 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -519,8 +519,7 @@ auditTrailService, failureHandler, threadPool, anonymousUser, getAuthorizationEn components.add(ipFilter.get()); DestructiveOperations destructiveOperations = new DestructiveOperations(settings, clusterService.getClusterSettings()); securityInterceptor.set(new SecurityServerTransportInterceptor(settings, threadPool, authcService.get(), - authzService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, - clusterService)); + authzService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, clusterService)); securityActionFilter.set(new SecurityActionFilter(authcService.get(), authzService, auditTrailService, getLicenseState(), threadPool, securityContext.get(), destructiveOperations)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 4b567b9c4c17f..93602c1a7b994 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -346,7 +346,6 @@ public void testAuthenticateBothSupportSecondSucceeds() throws Exception { SetOnce reqId = new SetOnce<>(); if (requestIdAlreadyPresent) { reqId.set(AuditUtil.getOrGenerateRequestId(threadContext)); - assertThat(reqId.get(), not(nullValue())); } final AtomicBoolean completed = new AtomicBoolean(false); From 6e2e8829c59242d5b956074abc2a8b3575d74c58 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Wed, 16 Dec 2020 11:38:16 +0200 Subject: [PATCH 26/26] Update x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java Co-authored-by: Tim Vernum --- .../xpack/security/action/filter/SecurityActionFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index 8291c9a489e4d..d1f08255b1eaf 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -124,8 +124,8 @@ public int order() { return Integer.MIN_VALUE; } - private void applyInternal(Task task, ActionFilterChain chain, String action, Request request, ActionListener listener) { + private void applyInternal(Task task, + ActionFilterChain chain, String action, Request request, ActionListener listener) { if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) { IndicesRequest indicesRequest = (IndicesRequest) request; try {