|
52 | 52 | import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.EmptyAuthorizationInfo;
|
53 | 53 | import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.IndexAuthorizationResult;
|
54 | 54 | import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine.RequestInfo;
|
55 |
| -import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField; |
56 | 55 | import org.elasticsearch.xpack.core.security.authz.ResolvedIndices;
|
57 | 56 | import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
|
58 | 57 | import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor;
|
|
85 | 84 |
|
86 | 85 | import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext;
|
87 | 86 | import static org.elasticsearch.xpack.core.security.SecurityField.setting;
|
| 87 | +import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.ACTION_SCOPE_AUTHORIZATION_KEYS; |
| 88 | +import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.AUTHORIZATION_INFO_KEY; |
| 89 | +import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.INDICES_PERMISSIONS_KEY; |
| 90 | +import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField.ORIGINATING_ACTION_KEY; |
88 | 91 | import static org.elasticsearch.xpack.core.security.support.Exceptions.authorizationError;
|
89 | 92 | import static org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME;
|
90 | 93 |
|
91 | 94 | public class AuthorizationService extends AbstractComponent {
|
92 | 95 | public static final Setting<Boolean> ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING =
|
93 | 96 | Setting.boolSetting(setting("authc.anonymous.authz_exception"), true, Property.NodeScope);
|
94 |
| - public static final String ORIGINATING_ACTION_KEY = "_originating_action_name"; |
95 |
| - public static final String AUTHORIZATION_INFO_KEY = "_authz_info"; |
96 | 97 | private static final AuthorizationInfo SYSTEM_AUTHZ_INFO =
|
97 | 98 | () -> Collections.singletonMap(PRINCIPAL_ROLES_FIELD_NAME, new String[] { SystemUser.ROLE_NAME });
|
98 | 99 |
|
@@ -160,39 +161,51 @@ private AuthorizationInfo getAuthorizationInfoFromContext() {
|
160 | 161 | */
|
161 | 162 | public void authorize(final Authentication authentication, final String action, final TransportRequest originalRequest,
|
162 | 163 | final ActionListener<Void> listener) throws ElasticsearchSecurityException {
|
163 |
| - // prior to doing any authorization lets set the originating action in the context only |
164 |
| - putTransientIfNonExisting(ORIGINATING_ACTION_KEY, action); |
165 |
| - |
166 |
| - String auditId = AuditUtil.extractRequestId(threadContext); |
167 |
| - if (auditId == null) { |
168 |
| - // We would like to assert that there is an existing request-id, but if this is a system action, then that might not be |
169 |
| - // true because the request-id is generated during authentication |
170 |
| - if (isInternalUser(authentication.getUser(), authentication.getVersion()) != false) { |
171 |
| - auditId = AuditUtil.getOrGenerateRequestId(threadContext); |
172 |
| - } else { |
173 |
| - auditTrail.tamperedRequest(null, authentication.getUser(), action, originalRequest); |
174 |
| - final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal() |
175 |
| - + "] without an existing request-id"; |
176 |
| - assert false : message; |
177 |
| - listener.onFailure(new ElasticsearchSecurityException(message)); |
| 164 | + /* authorization fills in certain transient headers, which must be observed in the listener (action handler execution) |
| 165 | + * as well, but which must not bleed across different action context (eg parent-child action contexts). |
| 166 | + * <p> |
| 167 | + * Therefore we begin by clearing the existing ones up, as they might already be set during the authorization of a |
| 168 | + * previous parent action that ran under the same thread context (also on the same node). |
| 169 | + * When the returned {@code StoredContext} is closed, ALL the original headers are restored. |
| 170 | + */ |
| 171 | + try (ThreadContext.StoredContext ignore = threadContext.newStoredContext(false, |
| 172 | + ACTION_SCOPE_AUTHORIZATION_KEYS)) { // this does not clear {@code AuthorizationServiceField.ORIGINATING_ACTION_KEY} |
| 173 | + // prior to doing any authorization lets set the originating action in the thread context |
| 174 | + // the originating action is the current action if no originating action has yet been set in the current thread context |
| 175 | + // if there is already an original action, that stays put (eg. the current action is a child action) |
| 176 | + putTransientIfNonExisting(ORIGINATING_ACTION_KEY, action); |
| 177 | + |
| 178 | + String auditId = AuditUtil.extractRequestId(threadContext); |
| 179 | + if (auditId == null) { |
| 180 | + // We would like to assert that there is an existing request-id, but if this is a system action, then that might not be |
| 181 | + // true because the request-id is generated during authentication |
| 182 | + if (isInternalUser(authentication.getUser(), authentication.getVersion()) != false) { |
| 183 | + auditId = AuditUtil.getOrGenerateRequestId(threadContext); |
| 184 | + } else { |
| 185 | + auditTrail.tamperedRequest(null, authentication.getUser(), action, originalRequest); |
| 186 | + final String message = "Attempt to authorize action [" + action + "] for [" + authentication.getUser().principal() |
| 187 | + + "] without an existing request-id"; |
| 188 | + assert false : message; |
| 189 | + listener.onFailure(new ElasticsearchSecurityException(message)); |
| 190 | + } |
178 | 191 | }
|
179 |
| - } |
180 | 192 |
|
181 |
| - // sometimes a request might be wrapped within another, which is the case for proxied |
182 |
| - // requests and concrete shard requests |
183 |
| - final TransportRequest unwrappedRequest = maybeUnwrapRequest(authentication, originalRequest, action, auditId); |
184 |
| - if (SystemUser.is(authentication.getUser())) { |
185 |
| - // this never goes async so no need to wrap the listener |
186 |
| - authorizeSystemUser(authentication, action, auditId, unwrappedRequest, listener); |
187 |
| - } else { |
188 |
| - final String finalAuditId = auditId; |
189 |
| - final RequestInfo requestInfo = new RequestInfo(authentication, unwrappedRequest, action); |
190 |
| - final ActionListener<AuthorizationInfo> authzInfoListener = wrapPreservingContext(ActionListener.wrap( |
191 |
| - authorizationInfo -> { |
192 |
| - putTransientIfNonExisting(AUTHORIZATION_INFO_KEY, authorizationInfo); |
193 |
| - maybeAuthorizeRunAs(requestInfo, finalAuditId, authorizationInfo, listener); |
194 |
| - }, listener::onFailure), threadContext); |
195 |
| - getAuthorizationEngine(authentication).resolveAuthorizationInfo(requestInfo, authzInfoListener); |
| 193 | + // sometimes a request might be wrapped within another, which is the case for proxied |
| 194 | + // requests and concrete shard requests |
| 195 | + final TransportRequest unwrappedRequest = maybeUnwrapRequest(authentication, originalRequest, action, auditId); |
| 196 | + if (SystemUser.is(authentication.getUser())) { |
| 197 | + // this never goes async so no need to wrap the listener |
| 198 | + authorizeSystemUser(authentication, action, auditId, unwrappedRequest, listener); |
| 199 | + } else { |
| 200 | + final String finalAuditId = auditId; |
| 201 | + final RequestInfo requestInfo = new RequestInfo(authentication, unwrappedRequest, action); |
| 202 | + final ActionListener<AuthorizationInfo> authzInfoListener = wrapPreservingContext(ActionListener.wrap( |
| 203 | + authorizationInfo -> { |
| 204 | + threadContext.putTransient(AUTHORIZATION_INFO_KEY, authorizationInfo); |
| 205 | + maybeAuthorizeRunAs(requestInfo, finalAuditId, authorizationInfo, listener); |
| 206 | + }, listener::onFailure), threadContext); |
| 207 | + getAuthorizationEngine(authentication).resolveAuthorizationInfo(requestInfo, authzInfoListener); |
| 208 | + } |
196 | 209 | }
|
197 | 210 | }
|
198 | 211 |
|
@@ -237,7 +250,7 @@ private void authorizeAction(final RequestInfo requestInfo, final String request
|
237 | 250 | if (ClusterPrivilege.ACTION_MATCHER.test(action)) {
|
238 | 251 | final ActionListener<AuthorizationResult> clusterAuthzListener =
|
239 | 252 | wrapPreservingContext(new AuthorizationResultListener<>(result -> {
|
240 |
| - putTransientIfNonExisting(AuthorizationServiceField.INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL); |
| 253 | + threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL); |
241 | 254 | listener.onResponse(null);
|
242 | 255 | }, listener::onFailure, requestInfo, requestId, authzInfo), threadContext);
|
243 | 256 | authzEngine.authorizeClusterAction(requestInfo, authzInfo, clusterAuthzListener);
|
@@ -281,8 +294,7 @@ private void handleIndexActionAuthorizationResult(final IndexAuthorizationResult
|
281 | 294 | final TransportRequest request = requestInfo.getRequest();
|
282 | 295 | final String action = requestInfo.getAction();
|
283 | 296 | if (result.getIndicesAccessControl() != null) {
|
284 |
| - putTransientIfNonExisting(AuthorizationServiceField.INDICES_PERMISSIONS_KEY, |
285 |
| - result.getIndicesAccessControl()); |
| 297 | + threadContext.putTransient(INDICES_PERMISSIONS_KEY, result.getIndicesAccessControl()); |
286 | 298 | }
|
287 | 299 | //if we are creating an index we need to authorize potential aliases created at the same time
|
288 | 300 | if (IndexPrivilege.CREATE_INDEX_MATCHER.test(action)) {
|
@@ -371,8 +383,8 @@ private AuthorizationEngine getAuthorizationEngineForUser(final User user, final
|
371 | 383 | private void authorizeSystemUser(final Authentication authentication, final String action, final String requestId,
|
372 | 384 | final TransportRequest request, final ActionListener<Void> listener) {
|
373 | 385 | if (SystemUser.isAuthorized(action)) {
|
374 |
| - putTransientIfNonExisting(AuthorizationServiceField.INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL); |
375 |
| - putTransientIfNonExisting(AUTHORIZATION_INFO_KEY, SYSTEM_AUTHZ_INFO); |
| 386 | + threadContext.putTransient(INDICES_PERMISSIONS_KEY, IndicesAccessControl.ALLOW_ALL); |
| 387 | + threadContext.putTransient(AUTHORIZATION_INFO_KEY, SYSTEM_AUTHZ_INFO); |
376 | 388 | auditTrail.accessGranted(requestId, authentication, action, request, SYSTEM_AUTHZ_INFO);
|
377 | 389 | listener.onResponse(null);
|
378 | 390 | } else {
|
|
0 commit comments