10
10
import org .apache .logging .log4j .Logger ;
11
11
import org .elasticsearch .ElasticsearchException ;
12
12
import org .elasticsearch .action .ActionListener ;
13
+ import org .elasticsearch .action .ActionRunnable ;
13
14
import org .elasticsearch .common .Strings ;
14
15
import org .elasticsearch .common .bytes .BytesReference ;
15
16
import org .elasticsearch .common .cache .Cache ;
65
66
import java .util .Objects ;
66
67
import java .util .Optional ;
67
68
import java .util .Set ;
69
+ import java .util .concurrent .Executor ;
68
70
import java .util .concurrent .atomic .AtomicLong ;
69
71
import java .util .function .Consumer ;
70
72
import java .util .stream .Collectors ;
@@ -91,6 +93,11 @@ public class CompositeRolesStore {
91
93
Property .NodeScope
92
94
);
93
95
private static final Logger logger = LogManager .getLogger (CompositeRolesStore .class );
96
+ /**
97
+ * See {@link #shouldForkRoleBuilding(Set)}
98
+ */
99
+ private static final int ROLE_DESCRIPTOR_FORK_THRESHOLD = 100 ;
100
+ private static final int INDEX_PRIVILEGE_FORK_THRESHOLD = 1000 ;
94
101
95
102
private final RoleProviders roleProviders ;
96
103
private final NativePrivilegeStore privilegeStore ;
@@ -106,6 +113,7 @@ public class CompositeRolesStore {
106
113
private final Map <String , Role > internalUserRoles ;
107
114
private final RestrictedIndices restrictedIndices ;
108
115
private final ThreadContext threadContext ;
116
+ private final Executor roleBuildingExecutor ;
109
117
110
118
public CompositeRolesStore (
111
119
Settings settings ,
@@ -118,6 +126,7 @@ public CompositeRolesStore(
118
126
ServiceAccountService serviceAccountService ,
119
127
DocumentSubsetBitsetCache dlsBitsetCache ,
120
128
RestrictedIndices restrictedIndices ,
129
+ Executor roleBuildingExecutor ,
121
130
Consumer <Collection <RoleDescriptor >> effectiveRoleDescriptorsConsumer
122
131
) {
123
132
this .roleProviders = roleProviders ;
@@ -179,6 +188,7 @@ public void providersChanged() {
179
188
);
180
189
this .anonymousUser = new AnonymousUser (settings );
181
190
this .threadContext = threadContext ;
191
+ this .roleBuildingExecutor = roleBuildingExecutor ;
182
192
}
183
193
184
194
public void getRoles (Authentication authentication , ActionListener <Tuple <Role , Role >> roleActionListener ) {
@@ -276,21 +286,70 @@ public void buildRoleFromRoleReference(RoleReference roleReference, ActionListen
276
286
} else if (RolesRetrievalResult .SUPERUSER == rolesRetrievalResult ) {
277
287
roleActionListener .onResponse (superuserRole );
278
288
} else {
279
- buildThenMaybeCacheRole (
280
- roleKey ,
281
- rolesRetrievalResult .getRoleDescriptors (),
282
- rolesRetrievalResult .getMissingRoles (),
283
- rolesRetrievalResult .isSuccess (),
284
- invalidationCounter ,
285
- ActionListener .wrap (roleActionListener ::onResponse , failureHandler )
286
- );
289
+ final ActionListener <Role > wrapped = ActionListener .wrap (roleActionListener ::onResponse , failureHandler );
290
+ if (shouldForkRoleBuilding (rolesRetrievalResult .getRoleDescriptors ())) {
291
+ roleBuildingExecutor .execute (
292
+ ActionRunnable .wrap (
293
+ wrapped ,
294
+ l -> buildThenMaybeCacheRole (
295
+ roleKey ,
296
+ rolesRetrievalResult .getRoleDescriptors (),
297
+ rolesRetrievalResult .getMissingRoles (),
298
+ rolesRetrievalResult .isSuccess (),
299
+ invalidationCounter ,
300
+ l
301
+ )
302
+ )
303
+ );
304
+ } else {
305
+ buildThenMaybeCacheRole (
306
+ roleKey ,
307
+ rolesRetrievalResult .getRoleDescriptors (),
308
+ rolesRetrievalResult .getMissingRoles (),
309
+ rolesRetrievalResult .isSuccess (),
310
+ invalidationCounter ,
311
+ wrapped
312
+ );
313
+ }
287
314
}
288
315
}, failureHandler ));
289
316
} else {
290
317
roleActionListener .onResponse (existing );
291
318
}
292
319
}
293
320
321
+ /**
322
+ * Uses heuristics such as presence of application privileges to determine if role building will be expensive
323
+ * and therefore warrants forking.
324
+ * Package-private for testing.
325
+ */
326
+ boolean shouldForkRoleBuilding (Set <RoleDescriptor > roleDescriptors ) {
327
+ // A role with many role descriptors is likely expensive to build
328
+ if (roleDescriptors .size () > ROLE_DESCRIPTOR_FORK_THRESHOLD ) {
329
+ return true ;
330
+ }
331
+ int totalIndexPrivileges = 0 ;
332
+ int totalRemoteIndexPrivileges = 0 ;
333
+ for (RoleDescriptor roleDescriptor : roleDescriptors ) {
334
+ // Application privileges can also result in big automata; it's difficult to determine how big application privileges
335
+ // are so err on the side of caution
336
+ if (roleDescriptor .hasApplicationPrivileges ()) {
337
+ return true ;
338
+ }
339
+ // Index privilege names or remote index privilege names can result in big and complex automata
340
+ totalIndexPrivileges += roleDescriptor .getIndicesPrivileges ().length ;
341
+ totalRemoteIndexPrivileges += roleDescriptor .getRemoteIndicesPrivileges ().length ;
342
+ if (totalIndexPrivileges > INDEX_PRIVILEGE_FORK_THRESHOLD || totalRemoteIndexPrivileges > INDEX_PRIVILEGE_FORK_THRESHOLD ) {
343
+ return true ;
344
+ }
345
+ // Likewise for FLS/DLS
346
+ if (roleDescriptor .isUsingDocumentOrFieldLevelSecurity ()) {
347
+ return true ;
348
+ }
349
+ }
350
+ return false ;
351
+ }
352
+
294
353
private static boolean includesSuperuserRole (RoleReference roleReference ) {
295
354
if (roleReference instanceof RoleReference .NamedRoleReference namedRoles ) {
296
355
return Arrays .asList (namedRoles .getRoleNames ()).contains (ReservedRolesStore .SUPERUSER_ROLE_DESCRIPTOR .getName ());
@@ -313,10 +372,11 @@ private void buildThenMaybeCacheRole(
313
372
ActionListener <Role > listener
314
373
) {
315
374
logger .trace (
316
- "Building role from descriptors [{}] for names [{}] from source [{}]" ,
375
+ "Building role from descriptors [{}] for names [{}] from source [{}] on [{}] " ,
317
376
roleDescriptors ,
318
377
roleKey .getNames (),
319
- roleKey .getSource ()
378
+ roleKey .getSource (),
379
+ Thread .currentThread ().getName ()
320
380
);
321
381
buildRoleFromDescriptors (
322
382
roleDescriptors ,
0 commit comments