Skip to content

Commit 8aadc7a

Browse files
authored
Change read-only suffix for file based role mappings (#114205) (#114222)
This changes the suffix for a role mapping to be `-read-only` instead of ` (read only)` since the name can be used (and will be by Kibana) to get a specific operator defined role mapping. This also removes the suffix when checking cluster state for the mapping. There is a slight risk that `<name>-read-only` exists both in the native store and as `<name>` in the file based mapping. I think that's very low risk, so didn't add any code to cover that case. (cherry picked from commit bc8f9dc) # Conflicts: # x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/role/TransportDeleteRoleAction.java
1 parent 3c02efe commit 8aadc7a

File tree

6 files changed

+58
-15
lines changed

6 files changed

+58
-15
lines changed

x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/RoleMappingFileSettingsIT.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,9 +359,9 @@ public void testGetRoleMappings() throws Exception {
359359
Arrays.stream(response.mappings()).map(ExpressionRoleMapping::getName).toList(),
360360
containsInAnyOrder(
361361
"everyone_kibana",
362-
"everyone_kibana " + RESERVED_ROLE_MAPPING_SUFFIX,
362+
"everyone_kibana" + RESERVED_ROLE_MAPPING_SUFFIX,
363363
"_everyone_kibana",
364-
"everyone_fleet " + RESERVED_ROLE_MAPPING_SUFFIX,
364+
"everyone_fleet" + RESERVED_ROLE_MAPPING_SUFFIX,
365365
"zzz_mapping",
366366
"123_mapping"
367367
)
@@ -378,6 +378,16 @@ public void testGetRoleMappings() throws Exception {
378378
// it's possible to delete overlapping native role mapping
379379
assertTrue(client().execute(DeleteRoleMappingAction.INSTANCE, deleteRequest("everyone_kibana")).actionGet().isFound());
380380

381+
// Fetch a specific file based role
382+
request = new GetRoleMappingsRequest();
383+
request.setNames("everyone_kibana" + RESERVED_ROLE_MAPPING_SUFFIX);
384+
response = client().execute(GetRoleMappingsAction.INSTANCE, request).get();
385+
assertTrue(response.hasMappings());
386+
assertThat(
387+
Arrays.stream(response.mappings()).map(ExpressionRoleMapping::getName).toList(),
388+
containsInAnyOrder("everyone_kibana" + RESERVED_ROLE_MAPPING_SUFFIX)
389+
);
390+
381391
savedClusterState = setupClusterStateListenerForCleanup(internalCluster().getMasterName());
382392
writeJSONFile(internalCluster().getMasterName(), emptyJSON, logger, versionCounter);
383393
awaitSuccessful = savedClusterState.v1().await(20, TimeUnit.SECONDS);
@@ -555,9 +565,7 @@ private static void assertGetResponseHasMappings(boolean readOnly, String... map
555565
assertThat(
556566
Arrays.stream(response.mappings()).map(ExpressionRoleMapping::getName).toList(),
557567
containsInAnyOrder(
558-
Arrays.stream(mappings)
559-
.map(mapping -> mapping + (readOnly ? " " + RESERVED_ROLE_MAPPING_SUFFIX : ""))
560-
.toArray(String[]::new)
568+
Arrays.stream(mappings).map(mapping -> mapping + (readOnly ? RESERVED_ROLE_MAPPING_SUFFIX : "")).toArray(String[]::new)
561569
)
562570
);
563571
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/role/TransportDeleteRoleAction.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
import org.elasticsearch.action.support.ActionFilters;
1212
import org.elasticsearch.action.support.TransportAction;
1313
import org.elasticsearch.common.inject.Inject;
14+
import org.elasticsearch.common.logging.HeaderWarning;
1415
import org.elasticsearch.tasks.Task;
1516
import org.elasticsearch.transport.TransportService;
1617
import org.elasticsearch.xpack.core.security.action.role.DeleteRoleAction;
1718
import org.elasticsearch.xpack.core.security.action.role.DeleteRoleRequest;
1819
import org.elasticsearch.xpack.core.security.action.role.DeleteRoleResponse;
20+
import org.elasticsearch.xpack.security.authc.support.mapper.ClusterStateRoleMapper;
1921
import org.elasticsearch.xpack.security.authz.ReservedRoleNameChecker;
2022
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
2123

@@ -24,16 +26,20 @@ public class TransportDeleteRoleAction extends TransportAction<DeleteRoleRequest
2426
private final NativeRolesStore rolesStore;
2527
private final ReservedRoleNameChecker reservedRoleNameChecker;
2628

29+
private final ClusterStateRoleMapper clusterStateRoleMapper;
30+
2731
@Inject
2832
public TransportDeleteRoleAction(
2933
ActionFilters actionFilters,
3034
NativeRolesStore rolesStore,
3135
TransportService transportService,
32-
ReservedRoleNameChecker reservedRoleNameChecker
36+
ReservedRoleNameChecker reservedRoleNameChecker,
37+
ClusterStateRoleMapper clusterStateRoleMapper
3338
) {
3439
super(DeleteRoleAction.NAME, actionFilters, transportService.getTaskManager());
3540
this.rolesStore = rolesStore;
3641
this.reservedRoleNameChecker = reservedRoleNameChecker;
42+
this.clusterStateRoleMapper = clusterStateRoleMapper;
3743
}
3844

3945
@Override
@@ -44,7 +50,19 @@ protected void doExecute(Task task, DeleteRoleRequest request, ActionListener<De
4450
}
4551

4652
try {
47-
rolesStore.deleteRole(request, listener.safeMap(DeleteRoleResponse::new));
53+
rolesStore.deleteRole(request, listener.safeMap((found) -> {
54+
if (clusterStateRoleMapper.hasMapping(request.name())) {
55+
// Allow to delete a mapping with the same name in the native role mapping store as the file_settings namespace, but
56+
// add a warning header to signal to the caller that this could be a problem.
57+
HeaderWarning.addWarning(
58+
"A read only role mapping with the same name ["
59+
+ request.name()
60+
+ "] has been previously been defined in a configuration file. "
61+
+ "The read only role mapping will still be active."
62+
);
63+
}
64+
return new DeleteRoleResponse(found);
65+
}));
4866
} catch (Exception e) {
4967
logger.error((Supplier<?>) () -> "failed to delete role [" + request.name() + "]", e);
5068
listener.onFailure(e);

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/rolemapping/TransportGetRoleMappingsAction.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.HashSet;
2626
import java.util.List;
2727
import java.util.Set;
28+
import java.util.stream.Collectors;
2829
import java.util.stream.Stream;
2930

3031
import static org.elasticsearch.xpack.security.authc.support.mapper.ClusterStateRoleMapper.RESERVED_ROLE_MAPPING_SUFFIX;
@@ -63,7 +64,12 @@ protected void doExecute(Task task, final GetRoleMappingsRequest request, final
6364
roleMappingStore.getRoleMappings(names, ActionListener.wrap(mappings -> {
6465
List<ExpressionRoleMapping> combinedRoleMappings = Stream.concat(
6566
mappings.stream(),
66-
clusterStateRoleMapper.getMappings(names)
67+
clusterStateRoleMapper.getMappings(names == null ? null : names.stream().map(name -> {
68+
// If a read-only role is fetched by name including suffix, remove suffix
69+
return name.endsWith(RESERVED_ROLE_MAPPING_SUFFIX)
70+
? name.substring(0, name.length() - RESERVED_ROLE_MAPPING_SUFFIX.length())
71+
: name;
72+
}).collect(Collectors.toSet()))
6773
.stream()
6874
.map(this::cloneAndMarkAsReadOnly)
6975
.sorted(Comparator.comparing(ExpressionRoleMapping::getName))
@@ -75,7 +81,7 @@ protected void doExecute(Task task, final GetRoleMappingsRequest request, final
7581
private ExpressionRoleMapping cloneAndMarkAsReadOnly(ExpressionRoleMapping mapping) {
7682
// Mark role mappings from cluster state as "read only" by adding a suffix to their name
7783
return new ExpressionRoleMapping(
78-
mapping.getName() + " " + RESERVED_ROLE_MAPPING_SUFFIX,
84+
mapping.getName() + RESERVED_ROLE_MAPPING_SUFFIX,
7985
mapping.getExpression(),
8086
mapping.getRoles(),
8187
mapping.getRoleTemplates(),

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/ClusterStateRoleMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class ClusterStateRoleMapper extends AbstractRoleMapperClearRealmCache im
4747
* </ul>
4848
*/
4949
public static final String CLUSTER_STATE_ROLE_MAPPINGS_ENABLED = "xpack.security.authc.cluster_state_role_mappings.enabled";
50-
public static final String RESERVED_ROLE_MAPPING_SUFFIX = "(read only)";
50+
public static final String RESERVED_ROLE_MAPPING_SUFFIX = "-read-only-operator-config";
5151
private static final Logger logger = LogManager.getLogger(ClusterStateRoleMapper.class);
5252

5353
private final ScriptService scriptService;

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/role/TransportDeleteRoleActionTests.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.elasticsearch.xpack.core.security.action.role.DeleteRoleResponse;
2020
import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper;
2121
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
22+
import org.elasticsearch.xpack.security.authc.support.mapper.ClusterStateRoleMapper;
2223
import org.elasticsearch.xpack.security.authz.ReservedRoleNameChecker;
2324
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
2425
import org.junit.BeforeClass;
@@ -66,7 +67,8 @@ public void testReservedRole() {
6667
mock(ActionFilters.class),
6768
rolesStore,
6869
transportService,
69-
new ReservedRoleNameChecker.Default()
70+
new ReservedRoleNameChecker.Default(),
71+
mock(ClusterStateRoleMapper.class)
7072
);
7173

7274
DeleteRoleRequest request = new DeleteRoleRequest();
@@ -115,7 +117,8 @@ private void testValidRole(String roleName) {
115117
mock(ActionFilters.class),
116118
rolesStore,
117119
transportService,
118-
new ReservedRoleNameChecker.Default()
120+
new ReservedRoleNameChecker.Default(),
121+
mock(ClusterStateRoleMapper.class)
119122
);
120123

121124
DeleteRoleRequest request = new DeleteRoleRequest();
@@ -168,7 +171,8 @@ public void testException() {
168171
mock(ActionFilters.class),
169172
rolesStore,
170173
transportService,
171-
new ReservedRoleNameChecker.Default()
174+
new ReservedRoleNameChecker.Default(),
175+
mock(ClusterStateRoleMapper.class)
172176
);
173177

174178
DeleteRoleRequest request = new DeleteRoleRequest();

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/rolemapping/TransportPutRoleMappingActionTests.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Set;
3030
import java.util.concurrent.atomic.AtomicReference;
3131

32+
import static org.elasticsearch.xpack.security.authc.support.mapper.ClusterStateRoleMapper.RESERVED_ROLE_MAPPING_SUFFIX;
3233
import static org.hamcrest.Matchers.aMapWithSize;
3334
import static org.hamcrest.Matchers.contains;
3435
import static org.hamcrest.Matchers.equalTo;
@@ -97,12 +98,18 @@ public void testPutMappingWithInvalidName() {
9798
final FieldExpression expression = new FieldExpression("username", Collections.singletonList(new FieldExpression.FieldValue("*")));
9899
IllegalArgumentException illegalArgumentException = expectThrows(
99100
IllegalArgumentException.class,
100-
() -> put("anarchy (read only)", expression, "superuser", Collections.singletonMap("dumb", true))
101+
() -> put("anarchy" + RESERVED_ROLE_MAPPING_SUFFIX, expression, "superuser", Collections.singletonMap("dumb", true))
101102
);
102103

103104
assertThat(
104105
illegalArgumentException.getMessage(),
105-
equalTo("Invalid mapping name [anarchy (read only)]. [(read only)] is not an allowed suffix")
106+
equalTo(
107+
"Invalid mapping name [anarchy"
108+
+ RESERVED_ROLE_MAPPING_SUFFIX
109+
+ "]. ["
110+
+ RESERVED_ROLE_MAPPING_SUFFIX
111+
+ "] is not an allowed suffix"
112+
)
106113
);
107114
}
108115

0 commit comments

Comments
 (0)