34
34
import org .elasticsearch .xpack .core .security .action .rolemapping .PutRoleMappingAction ;
35
35
import org .elasticsearch .xpack .core .security .action .rolemapping .PutRoleMappingRequest ;
36
36
import org .elasticsearch .xpack .core .security .action .rolemapping .PutRoleMappingRequestBuilder ;
37
+ import org .elasticsearch .xpack .core .security .action .rolemapping .PutRoleMappingResponse ;
37
38
import org .elasticsearch .xpack .core .security .authc .RealmConfig ;
38
39
import org .elasticsearch .xpack .core .security .authc .support .UserRoleMapper ;
39
40
import org .elasticsearch .xpack .core .security .authc .support .mapper .ExpressionRoleMapping ;
58
59
import static org .elasticsearch .indices .recovery .RecoverySettings .INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING ;
59
60
import static org .elasticsearch .xcontent .XContentType .JSON ;
60
61
import static org .elasticsearch .xpack .core .security .test .TestRestrictedIndices .INTERNAL_SECURITY_MAIN_INDEX_7 ;
62
+ import static org .elasticsearch .xpack .security .authc .support .mapper .ClusterStateRoleMapper .RESERVED_ROLE_MAPPING_SUFFIX ;
61
63
import static org .hamcrest .Matchers .allOf ;
62
- import static org .hamcrest .Matchers .contains ;
63
64
import static org .hamcrest .Matchers .containsInAnyOrder ;
64
65
import static org .hamcrest .Matchers .containsString ;
65
66
import static org .hamcrest .Matchers .empty ;
66
- import static org .hamcrest .Matchers .emptyArray ;
67
67
import static org .hamcrest .Matchers .equalTo ;
68
68
import static org .hamcrest .Matchers .hasSize ;
69
69
import static org .hamcrest .Matchers .notNullValue ;
@@ -270,21 +270,28 @@ private void assertRoleMappingsSaveOK(CountDownLatch savedClusterState, AtomicLo
270
270
assertThat (resolveRolesFuture .get (), containsInAnyOrder ("kibana_user" , "fleet_user" ));
271
271
}
272
272
273
- // the role mappings are not retrievable by the role mapping action (which only accesses "native" i.e. index-based role mappings)
274
- var request = new GetRoleMappingsRequest ();
275
- request .setNames ("everyone_kibana" , "everyone_fleet" );
276
- var response = client ().execute (GetRoleMappingsAction .INSTANCE , request ).get ();
277
- assertFalse (response .hasMappings ());
278
- assertThat (response .mappings (), emptyArray ());
279
-
280
- // role mappings (with the same names) can also be stored in the "native" store
281
- var putRoleMappingResponse = client ().execute (PutRoleMappingAction .INSTANCE , sampleRestRequest ("everyone_kibana" )).actionGet ();
282
- assertTrue (putRoleMappingResponse .isCreated ());
283
- putRoleMappingResponse = client ().execute (PutRoleMappingAction .INSTANCE , sampleRestRequest ("everyone_fleet" )).actionGet ();
284
- assertTrue (putRoleMappingResponse .isCreated ());
273
+ // the role mappings are retrievable by the role mapping action for BWC
274
+ assertGetResponseHasMappings (true , "everyone_kibana" , "everyone_fleet" );
275
+
276
+ // role mappings (with the same names) can be stored in the "native" store
277
+ {
278
+ PutRoleMappingResponse response = client ().execute (PutRoleMappingAction .INSTANCE , sampleRestRequest ("everyone_kibana" ))
279
+ .actionGet ();
280
+ assertTrue (response .isCreated ());
281
+ response = client ().execute (PutRoleMappingAction .INSTANCE , sampleRestRequest ("everyone_fleet" )).actionGet ();
282
+ assertTrue (response .isCreated ());
283
+ }
284
+ {
285
+ // deleting role mappings that exist in the native store and in cluster-state should result in success
286
+ var response = client ().execute (DeleteRoleMappingAction .INSTANCE , deleteRequest ("everyone_kibana" )).actionGet ();
287
+ assertTrue (response .isFound ());
288
+ response = client ().execute (DeleteRoleMappingAction .INSTANCE , deleteRequest ("everyone_fleet" )).actionGet ();
289
+ assertTrue (response .isFound ());
290
+ }
291
+
285
292
}
286
293
287
- public void testRoleMappingsApplied () throws Exception {
294
+ public void testClusterStateRoleMappingsAddedThenDeleted () throws Exception {
288
295
ensureGreen ();
289
296
290
297
var savedClusterState = setupClusterStateListener (internalCluster ().getMasterName (), "everyone_kibana" );
@@ -293,6 +300,12 @@ public void testRoleMappingsApplied() throws Exception {
293
300
assertRoleMappingsSaveOK (savedClusterState .v1 (), savedClusterState .v2 ());
294
301
logger .info ("---> cleanup cluster settings..." );
295
302
303
+ {
304
+ // Deleting non-existent native role mappings returns not found even if they exist in config file
305
+ var response = client ().execute (DeleteRoleMappingAction .INSTANCE , deleteRequest ("everyone_kibana" )).get ();
306
+ assertFalse (response .isFound ());
307
+ }
308
+
296
309
savedClusterState = setupClusterStateListenerForCleanup (internalCluster ().getMasterName ());
297
310
298
311
writeJSONFile (internalCluster ().getMasterName (), emptyJSON , logger , versionCounter );
@@ -307,48 +320,85 @@ public void testRoleMappingsApplied() throws Exception {
307
320
clusterStateResponse .getState ().metadata ().persistentSettings ().get (INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING .getKey ())
308
321
);
309
322
310
- // native role mappings are not affected by the removal of the cluster-state based ones
323
+ // cluster-state role mapping was removed and is not returned in the API anymore
311
324
{
312
325
var request = new GetRoleMappingsRequest ();
313
326
request .setNames ("everyone_kibana" , "everyone_fleet" );
314
327
var response = client ().execute (GetRoleMappingsAction .INSTANCE , request ).get ();
315
- assertTrue (response .hasMappings ());
316
- assertThat (
317
- Arrays .stream (response .mappings ()).map (ExpressionRoleMapping ::getName ).toList (),
318
- containsInAnyOrder ("everyone_kibana" , "everyone_fleet" )
319
- );
328
+ assertFalse (response .hasMappings ());
320
329
}
321
330
322
- // and roles are resolved based on the native role mappings
331
+ // no role mappings means no roles are resolved
323
332
for (UserRoleMapper userRoleMapper : internalCluster ().getInstances (UserRoleMapper .class )) {
324
333
PlainActionFuture <Set <String >> resolveRolesFuture = new PlainActionFuture <>();
325
334
userRoleMapper .resolveRoles (
326
335
new UserRoleMapper .UserData ("anyUsername" , null , List .of (), Map .of (), mock (RealmConfig .class )),
327
336
resolveRolesFuture
328
337
);
329
- assertThat (resolveRolesFuture .get (), contains ( "kibana_user_native" ));
338
+ assertThat (resolveRolesFuture .get (), empty ( ));
330
339
}
340
+ }
331
341
332
- {
333
- var request = new DeleteRoleMappingRequest ();
334
- request .setName ("everyone_kibana" );
335
- var response = client ().execute (DeleteRoleMappingAction .INSTANCE , request ).get ();
336
- assertTrue (response .isFound ());
337
- request = new DeleteRoleMappingRequest ();
338
- request .setName ("everyone_fleet" );
339
- response = client ().execute (DeleteRoleMappingAction .INSTANCE , request ).get ();
340
- assertTrue (response .isFound ());
342
+ public void testGetRoleMappings () throws Exception {
343
+ ensureGreen ();
344
+
345
+ final List <String > nativeMappings = List .of ("everyone_kibana" , "_everyone_kibana" , "zzz_mapping" , "123_mapping" );
346
+ for (var mapping : nativeMappings ) {
347
+ client ().execute (PutRoleMappingAction .INSTANCE , sampleRestRequest (mapping )).actionGet ();
341
348
}
342
349
343
- // no roles are resolved now, because both native and cluster-state based stores have been cleared
344
- for (UserRoleMapper userRoleMapper : internalCluster ().getInstances (UserRoleMapper .class )) {
345
- PlainActionFuture <Set <String >> resolveRolesFuture = new PlainActionFuture <>();
346
- userRoleMapper .resolveRoles (
347
- new UserRoleMapper .UserData ("anyUsername" , null , List .of (), Map .of (), mock (RealmConfig .class )),
348
- resolveRolesFuture
349
- );
350
- assertThat (resolveRolesFuture .get (), empty ());
350
+ var savedClusterState = setupClusterStateListener (internalCluster ().getMasterName (), "everyone_kibana" );
351
+ writeJSONFile (internalCluster ().getMasterName (), testJSON , logger , versionCounter );
352
+ boolean awaitSuccessful = savedClusterState .v1 ().await (20 , TimeUnit .SECONDS );
353
+ assertTrue (awaitSuccessful );
354
+
355
+ var request = new GetRoleMappingsRequest ();
356
+ var response = client ().execute (GetRoleMappingsAction .INSTANCE , request ).get ();
357
+ assertTrue (response .hasMappings ());
358
+ assertThat (
359
+ Arrays .stream (response .mappings ()).map (ExpressionRoleMapping ::getName ).toList (),
360
+ containsInAnyOrder (
361
+ "everyone_kibana" ,
362
+ "everyone_kibana " + RESERVED_ROLE_MAPPING_SUFFIX ,
363
+ "_everyone_kibana" ,
364
+ "everyone_fleet " + RESERVED_ROLE_MAPPING_SUFFIX ,
365
+ "zzz_mapping" ,
366
+ "123_mapping"
367
+ )
368
+ );
369
+
370
+ int readOnlyCount = 0 ;
371
+ // assert that cluster-state role mappings come last
372
+ for (ExpressionRoleMapping mapping : response .mappings ()) {
373
+ readOnlyCount = mapping .getName ().endsWith (RESERVED_ROLE_MAPPING_SUFFIX ) ? readOnlyCount + 1 : readOnlyCount ;
351
374
}
375
+ // Two sourced from cluster-state
376
+ assertEquals (readOnlyCount , 2 );
377
+
378
+ // it's possible to delete overlapping native role mapping
379
+ assertTrue (client ().execute (DeleteRoleMappingAction .INSTANCE , deleteRequest ("everyone_kibana" )).actionGet ().isFound ());
380
+
381
+ savedClusterState = setupClusterStateListenerForCleanup (internalCluster ().getMasterName ());
382
+ writeJSONFile (internalCluster ().getMasterName (), emptyJSON , logger , versionCounter );
383
+ awaitSuccessful = savedClusterState .v1 ().await (20 , TimeUnit .SECONDS );
384
+ assertTrue (awaitSuccessful );
385
+
386
+ final ClusterStateResponse clusterStateResponse = clusterAdmin ().state (
387
+ new ClusterStateRequest ().waitForMetadataVersion (savedClusterState .v2 ().get ())
388
+ ).get ();
389
+
390
+ assertNull (
391
+ clusterStateResponse .getState ().metadata ().persistentSettings ().get (INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING .getKey ())
392
+ );
393
+
394
+ // Make sure remaining native mappings can still be fetched
395
+ request = new GetRoleMappingsRequest ();
396
+ response = client ().execute (GetRoleMappingsAction .INSTANCE , request ).get ();
397
+ assertTrue (response .hasMappings ());
398
+ assertThat (
399
+ Arrays .stream (response .mappings ()).map (ExpressionRoleMapping ::getName ).toList (),
400
+ containsInAnyOrder ("_everyone_kibana" , "zzz_mapping" , "123_mapping" )
401
+ );
352
402
}
353
403
354
404
public static Tuple <CountDownLatch , AtomicLong > setupClusterStateListenerForError (
@@ -433,11 +483,8 @@ public void testRoleMappingApplyWithSecurityIndexClosed() throws Exception {
433
483
boolean awaitSuccessful = savedClusterState .v1 ().await (20 , TimeUnit .SECONDS );
434
484
assertTrue (awaitSuccessful );
435
485
436
- // no native role mappings exist
437
- var request = new GetRoleMappingsRequest ();
438
- request .setNames ("everyone_kibana" , "everyone_fleet" );
439
- var response = client ().execute (GetRoleMappingsAction .INSTANCE , request ).get ();
440
- assertFalse (response .hasMappings ());
486
+ // even if index is closed, cluster-state role mappings are still returned
487
+ assertGetResponseHasMappings (true , "everyone_kibana" , "everyone_fleet" );
441
488
442
489
// cluster state settings are also applied
443
490
var clusterStateResponse = clusterAdmin ().state (new ClusterStateRequest ().waitForMetadataVersion (savedClusterState .v2 ().get ()))
@@ -475,6 +522,12 @@ public void testRoleMappingApplyWithSecurityIndexClosed() throws Exception {
475
522
}
476
523
}
477
524
525
+ private DeleteRoleMappingRequest deleteRequest (String name ) {
526
+ var request = new DeleteRoleMappingRequest ();
527
+ request .setName (name );
528
+ return request ;
529
+ }
530
+
478
531
private PutRoleMappingRequest sampleRestRequest (String name ) throws Exception {
479
532
var json = """
480
533
{
@@ -493,4 +546,19 @@ private PutRoleMappingRequest sampleRestRequest(String name) throws Exception {
493
546
return new PutRoleMappingRequestBuilder (null ).source (name , parser ).request ();
494
547
}
495
548
}
549
+
550
+ private static void assertGetResponseHasMappings (boolean readOnly , String ... mappings ) throws InterruptedException , ExecutionException {
551
+ var request = new GetRoleMappingsRequest ();
552
+ request .setNames (mappings );
553
+ var response = client ().execute (GetRoleMappingsAction .INSTANCE , request ).get ();
554
+ assertTrue (response .hasMappings ());
555
+ assertThat (
556
+ Arrays .stream (response .mappings ()).map (ExpressionRoleMapping ::getName ).toList (),
557
+ containsInAnyOrder (
558
+ Arrays .stream (mappings )
559
+ .map (mapping -> mapping + (readOnly ? " " + RESERVED_ROLE_MAPPING_SUFFIX : "" ))
560
+ .toArray (String []::new )
561
+ )
562
+ );
563
+ }
496
564
}
0 commit comments