Skip to content

Commit bd9ba12

Browse files
Add new audit handler method for action responses (#63708)
This adds a new method to the AuditTrail that intercepts the responses of transport-level actions. This new method is unlike all the other existing audit methods because it is called after the action has been run (so that it has access to the response). After careful deliberation, the new method is called for the responses of actions that are intercepted by the `SecurityActionFilter` only, and not by the transport filter. In order to facilitate the "linking" of the new audit event with the other existing events, the audit method receives the requestId as well as the authentication as arguments (in addition to the request itself and the response). This is labeled non-issue because it is only the foundation upon which later features that actually print out (some) responses can be built upon. Related #63221
1 parent 15e456d commit bd9ba12

File tree

9 files changed

+585
-147
lines changed

9 files changed

+585
-147
lines changed

x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MlUpgradeModeActionFilterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public void testApply_ActionAllowedInUpgradeMode() {
9797

9898
public void testOrder_UpgradeFilterIsExecutedAfterSecurityFilter() {
9999
MlUpgradeModeActionFilter upgradeModeFilter = new MlUpgradeModeActionFilter(clusterService);
100-
SecurityActionFilter securityFilter = new SecurityActionFilter(null, null, null, mock(ThreadPool.class), null, null);
100+
SecurityActionFilter securityFilter = new SecurityActionFilter(null, null, null, null, mock(ThreadPool.class), null, null);
101101

102102
ActionFilter[] actionFiltersInOrderOfExecution = new ActionFilters(Sets.newHashSet(upgradeModeFilter, securityFilter)).filters();
103103
assertThat(actionFiltersInOrderOfExecution, is(arrayContaining(securityFilter, upgradeModeFilter)));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ auditTrailService, failureHandler, threadPool, anonymousUser, getAuthorizationEn
560560
securityInterceptor.set(new SecurityServerTransportInterceptor(settings, threadPool, authcService.get(),
561561
authzService, getLicenseState(), getSslService(), securityContext.get(), destructiveOperations, clusterService));
562562

563-
securityActionFilter.set(new SecurityActionFilter(authcService.get(), authzService, getLicenseState(),
563+
securityActionFilter.set(new SecurityActionFilter(authcService.get(), authzService, auditTrailService, getLicenseState(),
564564
threadPool, securityContext.get(), destructiveOperations));
565565

566566
return components;

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.action.support.ActionFilterChain;
2121
import org.elasticsearch.action.support.ContextPreservingActionListener;
2222
import org.elasticsearch.action.support.DestructiveOperations;
23+
import org.elasticsearch.common.Strings;
2324
import org.elasticsearch.common.util.concurrent.ThreadContext;
2425
import org.elasticsearch.license.LicenseUtils;
2526
import org.elasticsearch.license.XPackLicenseState;
@@ -33,11 +34,12 @@
3334
import org.elasticsearch.xpack.core.security.support.Automatons;
3435
import org.elasticsearch.xpack.core.security.user.SystemUser;
3536
import org.elasticsearch.xpack.security.action.SecurityActionMapper;
37+
import org.elasticsearch.xpack.security.audit.AuditTrailService;
38+
import org.elasticsearch.xpack.security.audit.AuditUtil;
3639
import org.elasticsearch.xpack.security.authc.AuthenticationService;
3740
import org.elasticsearch.xpack.security.authz.AuthorizationService;
3841
import org.elasticsearch.xpack.security.authz.AuthorizationUtils;
3942

40-
import java.io.IOException;
4143
import java.util.function.Predicate;
4244

4345
public class SecurityActionFilter implements ActionFilter {
@@ -48,17 +50,19 @@ public class SecurityActionFilter implements ActionFilter {
4850

4951
private final AuthenticationService authcService;
5052
private final AuthorizationService authzService;
53+
private final AuditTrailService auditTrailService;
5154
private final SecurityActionMapper actionMapper = new SecurityActionMapper();
5255
private final XPackLicenseState licenseState;
5356
private final ThreadContext threadContext;
5457
private final SecurityContext securityContext;
5558
private final DestructiveOperations destructiveOperations;
5659

5760
public SecurityActionFilter(AuthenticationService authcService, AuthorizationService authzService,
58-
XPackLicenseState licenseState, ThreadPool threadPool,
61+
AuditTrailService auditTrailService, XPackLicenseState licenseState, ThreadPool threadPool,
5962
SecurityContext securityContext, DestructiveOperations destructiveOperations) {
6063
this.authcService = authcService;
6164
this.authzService = authzService;
65+
this.auditTrailService = auditTrailService;
6266
this.licenseState = licenseState;
6367
this.threadContext = threadPool.getThreadContext();
6468
this.securityContext = securityContext;
@@ -83,29 +87,19 @@ public <Request extends ActionRequest, Response extends ActionResponse> void app
8387
if (licenseState.isSecurityEnabled()) {
8488
final ActionListener<Response> contextPreservingListener =
8589
ContextPreservingActionListener.wrapPreservingContext(listener, threadContext);
86-
ActionListener<Void> authenticatedListener = ActionListener.wrap(
87-
(aVoid) -> chain.proceed(task, action, request, contextPreservingListener), contextPreservingListener::onFailure);
8890
final boolean useSystemUser = AuthorizationUtils.shouldReplaceUserWithSystem(threadContext, action);
8991
try {
9092
if (useSystemUser) {
9193
securityContext.executeAsUser(SystemUser.INSTANCE, (original) -> {
92-
try {
93-
applyInternal(action, request, authenticatedListener);
94-
} catch (IOException e) {
95-
listener.onFailure(e);
96-
}
94+
applyInternal(task, chain, action, request, contextPreservingListener);
9795
}, Version.CURRENT);
9896
} else if (AuthorizationUtils.shouldSetUserBasedOnActionOrigin(threadContext)) {
9997
AuthorizationUtils.switchUserBasedOnActionOriginAndExecute(threadContext, securityContext, (original) -> {
100-
try {
101-
applyInternal(action, request, authenticatedListener);
102-
} catch (IOException e) {
103-
listener.onFailure(e);
104-
}
98+
applyInternal(task, chain, action, request, contextPreservingListener);
10599
});
106100
} else {
107101
try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(true)) {
108-
applyInternal(action, request, authenticatedListener);
102+
applyInternal(task, chain, action, request, contextPreservingListener);
109103
}
110104
}
111105
} catch (Exception e) {
@@ -130,13 +124,13 @@ public int order() {
130124
return Integer.MIN_VALUE;
131125
}
132126

133-
private <Request extends ActionRequest> void applyInternal(String action, Request request,
134-
ActionListener<Void> listener) throws IOException {
127+
private <Request extends ActionRequest, Response extends ActionResponse> void applyInternal(Task task,
128+
ActionFilterChain<Request, Response> chain, String action, Request request, ActionListener<Response> listener) {
135129
if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) {
136130
IndicesRequest indicesRequest = (IndicesRequest) request;
137131
try {
138132
destructiveOperations.failDestructive(indicesRequest.indices());
139-
} catch(IllegalArgumentException e) {
133+
} catch (IllegalArgumentException e) {
140134
listener.onFailure(e);
141135
return;
142136
}
@@ -156,7 +150,17 @@ it to the action without an associated user (not via REST or transport - this is
156150
authcService.authenticate(securityAction, request, SystemUser.INSTANCE,
157151
ActionListener.wrap((authc) -> {
158152
if (authc != null) {
159-
authorizeRequest(authc, securityAction, request, listener);
153+
final String requestId = AuditUtil.extractRequestId(threadContext);
154+
assert Strings.hasText(requestId);
155+
authorizeRequest(authc, securityAction, request, ActionListener.delegateFailure(listener,
156+
(ignore, aVoid) -> {
157+
chain.proceed(task, action, request, ActionListener.delegateFailure(listener,
158+
(ignore2, response) -> {
159+
auditTrailService.get().coordinatingActionResponse(requestId, authc, action, request,
160+
response);
161+
listener.onResponse(response);
162+
}));
163+
}));
160164
} else if (licenseState.isSecurityEnabled() == false) {
161165
listener.onResponse(null);
162166
} else {
@@ -166,12 +170,11 @@ it to the action without an associated user (not via REST or transport - this is
166170
}
167171

168172
private <Request extends ActionRequest> void authorizeRequest(Authentication authentication, String securityAction, Request request,
169-
ActionListener<Void> listener) {
173+
ActionListener<Void> listener) {
170174
if (authentication == null) {
171175
listener.onFailure(new IllegalArgumentException("authentication must be non null for authorization"));
172176
} else {
173-
authzService.authorize(authentication, securityAction, request, ActionListener.wrap(ignore -> listener.onResponse(null),
174-
listener::onFailure));
177+
authzService.authorize(authentication, securityAction, request, listener);
175178
}
176179
}
177180
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.elasticsearch.common.transport.TransportAddress;
99
import org.elasticsearch.rest.RestRequest;
1010
import org.elasticsearch.transport.TransportRequest;
11+
import org.elasticsearch.transport.TransportResponse;
1112
import org.elasticsearch.xpack.core.security.authc.Authentication;
1213
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
1314
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo;
@@ -81,4 +82,9 @@ void runAsDenied(String requestId, Authentication authentication, RestRequest re
8182
void explicitIndexAccessEvent(String requestId, AuditLevel eventType, Authentication authentication, String action, String indices,
8283
String requestName, TransportAddress remoteAddress, AuthorizationInfo authorizationInfo);
8384

85+
// this is the only audit method that is called *after* the action executed, when the response is available
86+
// it is however *only called for coordinating actions*, which are the actions that a client invokes as opposed to
87+
// the actions that a node invokes in order to service a client request
88+
void coordinatingActionResponse(String requestId, Authentication authentication, String action, TransportRequest transportRequest,
89+
TransportResponse transportResponse);
8490
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.license.XPackLicenseState.Feature;
1313
import org.elasticsearch.rest.RestRequest;
1414
import org.elasticsearch.transport.TransportRequest;
15+
import org.elasticsearch.transport.TransportResponse;
1516
import org.elasticsearch.xpack.core.security.authc.Authentication;
1617
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
1718
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.AuthorizationInfo;
@@ -147,6 +148,11 @@ public void runAsDenied(String requestId, Authentication authentication, RestReq
147148
public void explicitIndexAccessEvent(String requestId, AuditLevel eventType, Authentication authentication,
148149
String action, String indices, String requestName, TransportAddress remoteAddress,
149150
AuthorizationInfo authorizationInfo) {}
151+
152+
@Override
153+
public void coordinatingActionResponse(String requestId, Authentication authentication, String action,
154+
TransportRequest transportRequest,
155+
TransportResponse transportResponse) { }
150156
}
151157

152158
private static class CompositeAuditTrail implements AuditTrail {
@@ -254,6 +260,15 @@ public void accessDenied(String requestId, Authentication authentication, String
254260
}
255261
}
256262

263+
@Override
264+
public void coordinatingActionResponse(String requestId, Authentication authentication, String action,
265+
TransportRequest transportRequest,
266+
TransportResponse transportResponse) {
267+
for (AuditTrail auditTrail : auditTrails) {
268+
auditTrail.coordinatingActionResponse(requestId, authentication, action, transportRequest, transportResponse);
269+
}
270+
}
271+
257272
@Override
258273
public void tamperedRequest(String requestId, RestRequest request) {
259274
for (AuditTrail auditTrail : auditTrails) {

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.elasticsearch.tasks.Task;
4040
import org.elasticsearch.threadpool.ThreadPool;
4141
import org.elasticsearch.transport.TransportRequest;
42+
import org.elasticsearch.transport.TransportResponse;
4243
import org.elasticsearch.xpack.core.security.action.CreateApiKeyAction;
4344
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
4445
import org.elasticsearch.xpack.core.security.action.GrantApiKeyAction;
@@ -815,6 +816,13 @@ public void runAsDenied(String requestId, Authentication authentication, RestReq
815816
}
816817
}
817818

819+
@Override
820+
public void coordinatingActionResponse(String requestId, Authentication authentication, String action,
821+
TransportRequest transportRequest,
822+
TransportResponse transportResponse) {
823+
// not implemented yet
824+
}
825+
818826
private LogEntryBuilder securityChangeLogEntryBuilder(String requestId) {
819827
return new LogEntryBuilder(false)
820828
.with(EVENT_TYPE_FIELD_NAME, SECURITY_CHANGE_ORIGIN_FIELD_VALUE)

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

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,8 @@
6161
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
6262
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
6363
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
64-
import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
6564
import org.elasticsearch.xpack.core.security.user.SystemUser;
6665
import org.elasticsearch.xpack.core.security.user.User;
67-
import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
68-
import org.elasticsearch.xpack.core.security.user.XPackUser;
6966
import org.elasticsearch.xpack.security.audit.AuditLevel;
7067
import org.elasticsearch.xpack.security.audit.AuditTrail;
7168
import org.elasticsearch.xpack.security.audit.AuditTrailService;
@@ -95,6 +92,7 @@
9592
import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.INDICES_PERMISSIONS_KEY;
9693
import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.ORIGINATING_ACTION_KEY;
9794
import static org.elasticsearch.xpack.core.security.support.Exceptions.authorizationError;
95+
import static org.elasticsearch.xpack.core.security.user.User.isInternal;
9896
import static org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME;
9997

10098
public class AuthorizationService {
@@ -190,14 +188,15 @@ public void authorize(final Authentication authentication, final String action,
190188
if (auditId == null) {
191189
// We would like to assert that there is an existing request-id, but if this is a system action, then that might not be
192190
// true because the request-id is generated during authentication
193-
if (isInternalUser(authentication.getUser()) != false) {
191+
if (isInternal(authentication.getUser())) {
194192
auditId = AuditUtil.getOrGenerateRequestId(threadContext);
195193
} else {
196194
auditTrailService.get().tamperedRequest(null, authentication, action, originalRequest);
197195
final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal()
198196
+ "] without an existing request-id";
199197
assert false : message;
200198
listener.onFailure(new ElasticsearchSecurityException(message));
199+
return;
201200
}
202201
}
203202

@@ -397,7 +396,7 @@ AuthorizationEngine getAuthorizationEngine(final Authentication authentication)
397396
private AuthorizationEngine getAuthorizationEngineForUser(final User user) {
398397
if (rbacEngine != authorizationEngine && licenseState.isSecurityEnabled() &&
399398
licenseState.checkFeature(Feature.SECURITY_AUTHORIZATION_ENGINE)) {
400-
if (ClientReservedRealm.isReserved(user.principal(), settings) || isInternalUser(user)) {
399+
if (ClientReservedRealm.isReserved(user.principal(), settings) || isInternal(user)) {
401400
return rbacEngine;
402401
} else {
403402
return authorizationEngine;
@@ -448,10 +447,6 @@ private TransportRequest maybeUnwrapRequest(Authentication authentication, Trans
448447
return request;
449448
}
450449

451-
private boolean isInternalUser(User user) {
452-
return SystemUser.is(user) || XPackUser.is(user) || XPackSecurityUser.is(user) || AsyncSearchUser.is(user);
453-
}
454-
455450
private void authorizeRunAs(final RequestInfo requestInfo, final AuthorizationInfo authzInfo,
456451
final ActionListener<AuthorizationResult> listener) {
457452
final Authentication authentication = requestInfo.getAuthentication();

0 commit comments

Comments
 (0)