Browse Source

Deprecating kibana_user and kibana_dashboard_only_user roles (#46456)

This change adds a new `kibana_admin` role, and deprecates
the old `kibana_user` and`kibana_dashboard_only_user`roles.

The deprecation is implemented via a new reserved metadata
attribute, which can be consumed from the API and also triggers
deprecation logging when used (by a user authenticating to
Elasticsearch).

Some docs have been updated to avoid references to these
deprecated roles.

Co-authored-by: Tim Vernum <tim@adjective.org>
Co-authored-by: Larry Gregory <legrego@users.noreply.github.com>
Larry Gregory 5 years ago
parent
commit
fa4869a94b

+ 2 - 2
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -694,8 +694,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
 
 
             List<Role> roles = response.getRoles();
             List<Role> roles = response.getRoles();
             assertNotNull(response);
             assertNotNull(response);
-            // 28 system roles plus the three we created
-            assertThat(roles.size(), equalTo(28 + 3));
+            // 29 system roles plus the three we created
+            assertThat(roles.size(), equalTo(29 + 3));
         }
         }
 
 
         {
         {

+ 1 - 1
docs/reference/monitoring/configuring-filebeat.asciidoc

@@ -117,7 +117,7 @@ If {security-features} are enabled, you must provide a valid user ID and
 password so that {filebeat} can connect to {kib}: 
 password so that {filebeat} can connect to {kib}: 
 
 
 .. Create a user on the monitoring cluster that has the 
 .. Create a user on the monitoring cluster that has the 
-<<built-in-roles,`kibana_user` built-in role>> or equivalent
+<<built-in-roles,`kibana_admin` built-in role>> or equivalent
 privileges.
 privileges.
 
 
 .. Add the `username` and `password` settings to the {es} output information in 
 .. Add the `username` and `password` settings to the {es} output information in 

+ 7 - 3
x-pack/docs/en/security/authentication/oidc-guide.asciidoc

@@ -420,14 +420,14 @@ through either the
 NOTE: You cannot use <<mapping-roles-file,role mapping files>>
 NOTE: You cannot use <<mapping-roles-file,role mapping files>>
 to grant roles to users authenticating via OpenID Connect.
 to grant roles to users authenticating via OpenID Connect.
 
 
-This is an example of a simple role mapping that grants the `kibana_user` role
+This is an example of a simple role mapping that grants the `example_role` role
 to any user who authenticates against the `oidc1` OpenID Connect realm:
 to any user who authenticates against the `oidc1` OpenID Connect realm:
 
 
 [source,console]
 [source,console]
 --------------------------------------------------
 --------------------------------------------------
-PUT /_security/role_mapping/oidc-kibana
+PUT /_security/role_mapping/oidc-example
 {
 {
-  "roles": [ "kibana_user" ],
+  "roles": [ "example_role" ], <1>
   "enabled": true,
   "enabled": true,
   "rules": {
   "rules": {
     "field": { "realm.name": "oidc1" }
     "field": { "realm.name": "oidc1" }
@@ -435,6 +435,10 @@ PUT /_security/role_mapping/oidc-kibana
 }
 }
 --------------------------------------------------
 --------------------------------------------------
 
 
+<1> The `example_role` role is *not* a builtin Elasticsearch role.
+This example assumes that you have created a custom role of your own, with
+appropriate access to your <<roles-indices-priv,indices>> and
+{kibana-ref}/kibana-privileges.html#kibana-feature-privileges[Kibana features].
 
 
 The user properties that are mapped via the realm configuration are used to process
 The user properties that are mapped via the realm configuration are used to process
 role mapping rules, and these rules determine which roles a user is granted.
 role mapping rules, and these rules determine which roles a user is granted.

+ 7 - 3
x-pack/docs/en/security/authentication/saml-guide.asciidoc

@@ -639,14 +639,14 @@ through either the
 NOTE: You cannot use <<mapping-roles-file,role mapping files>>
 NOTE: You cannot use <<mapping-roles-file,role mapping files>>
 to grant roles to users authenticating via SAML.
 to grant roles to users authenticating via SAML.
 
 
-This is an example of a simple role mapping that grants the `kibana_user` role
+This is an example of a simple role mapping that grants the `example_role` role
 to any user who authenticates against the `saml1` realm:
 to any user who authenticates against the `saml1` realm:
 
 
 [source,console]
 [source,console]
 --------------------------------------------------
 --------------------------------------------------
-PUT /_security/role_mapping/saml-kibana
+PUT /_security/role_mapping/saml-example
 {
 {
-  "roles": [ "kibana_user" ],
+  "roles": [ "example_role" ], <1>
   "enabled": true,
   "enabled": true,
   "rules": {
   "rules": {
     "field": { "realm.name": "saml1" }
     "field": { "realm.name": "saml1" }
@@ -654,6 +654,10 @@ PUT /_security/role_mapping/saml-kibana
 }
 }
 --------------------------------------------------
 --------------------------------------------------
 
 
+<1> The `example_role` role is *not* a builtin Elasticsearch role.
+This example assumes that you have created a custom role of your own, with
+appropriate access to your <<roles-indices-priv,indices>> and
+{kibana-ref}/kibana-privileges.html#kibana-feature-privileges[Kibana features].
 
 
 The attributes that are mapped via the realm configuration are used to process
 The attributes that are mapped via the realm configuration are used to process
 role mapping rules, and these rules determine which roles a user is granted.
 role mapping rules, and these rules determine which roles a user is granted.

+ 20 - 10
x-pack/docs/en/security/authorization/built-in-roles.asciidoc

@@ -72,10 +72,12 @@ NOTE: This role does *not* provide the ability to create indices; those privileg
 must be defined in a separate role.
 must be defined in a separate role.
 
 
 [[built-in-roles-kibana-dashboard]] `kibana_dashboard_only_user` ::
 [[built-in-roles-kibana-dashboard]] `kibana_dashboard_only_user` ::
-Grants access to the {kib} Dashboard and read-only permissions to Kibana.
-This role does not have access to editing tools in {kib}. For more
-information, see
-{kibana-ref}/xpack-dashboard-only-mode.html[{kib} Dashboard Only Mode].
+(This role is deprecated, please use 
+{kibana-ref}/kibana-privileges.html#kibana-feature-privileges[{kib} feature privileges]
+instead).
+Grants read-only access to the {kib} Dashboard in every 
+{kibana-ref}/xpack-spaces.html[space in {kib}].
+This role does not have access to editing tools in {kib}. 
 
 
 [[built-in-roles-kibana-system]] `kibana_system` ::
 [[built-in-roles-kibana-system]] `kibana_system` ::
 Grants access necessary for the {kib} system user to read from and write to the
 Grants access necessary for the {kib} system user to read from and write to the
@@ -87,9 +89,15 @@ see {kibana-ref}/using-kibana-with-security.html[Configuring Security in {kib}].
 NOTE: This role should not be assigned to users as the granted permissions may
 NOTE: This role should not be assigned to users as the granted permissions may
 change between releases.
 change between releases.
 
 
+[[built-in-roles-kibana-admin]] `kibana_admin`::
+Grants access to all features in {kib}. For more information on {kib} authorization,
+see {kibana-ref}/xpack-security-authorization.html[Kibana authorization].
+
 [[built-in-roles-kibana-user]] `kibana_user`::
 [[built-in-roles-kibana-user]] `kibana_user`::
-Grants access to all features in {kib}. For more information on Kibana authorization,
-see {kibana-ref}/xpack-security-authorization.html[Kibana Authorization].
+(This role is deprecated, please use the
+<<built-in-roles-kibana-admin,`kibana_admin`>> role instead.)
+Grants access to all features in {kib}. For more information on {kib} authorization,
+see {kibana-ref}/xpack-security-authorization.html[Kibana authorization].
 
 
 [[built-in-roles-logstash-admin]] `logstash_admin` ::
 [[built-in-roles-logstash-admin]] `logstash_admin` ::
 Grants access to the `.logstash*` indices for managing configurations.
 Grants access to the `.logstash*` indices for managing configurations.
@@ -127,7 +135,8 @@ Grants the minimum privileges required for any user of {monitoring} other than t
 required to use {kib}. This role grants access to the monitoring indices and grants
 required to use {kib}. This role grants access to the monitoring indices and grants
 privileges necessary for reading basic cluster information. This role also includes
 privileges necessary for reading basic cluster information. This role also includes
 all {kibana-ref}/kibana-privileges.html[Kibana privileges] for the {stack-monitor-features}.
 all {kibana-ref}/kibana-privileges.html[Kibana privileges] for the {stack-monitor-features}.
-Monitoring users should also be assigned the `kibana_user` role.
+Monitoring users should also be assigned the `kibana_admin` role, or another role
+with {kibana-ref}/xpack-security-authorization.html[access to the {kib} instance].
 
 
 [[built-in-roles-remote-monitoring-agent]] `remote_monitoring_agent`::
 [[built-in-roles-remote-monitoring-agent]] `remote_monitoring_agent`::
 Grants the minimum privileges required to write data into the monitoring indices
 Grants the minimum privileges required to write data into the monitoring indices
@@ -140,9 +149,10 @@ Grants the minimum privileges required to collect monitoring data for the {stack
 [[built-in-roles-reporting-user]] `reporting_user`::
 [[built-in-roles-reporting-user]] `reporting_user`::
 Grants the specific privileges required for users of {reporting} other than those
 Grants the specific privileges required for users of {reporting} other than those
 required to use {kib}. This role grants access to the reporting indices; each 
 required to use {kib}. This role grants access to the reporting indices; each 
-user has access to only their own reports. Reporting users should also be 
-assigned the `kibana_user` role and a role that grants them access to the data 
-that will be used to generate reports.
+user has access to only their own reports.
+Reporting users should also be assigned additional roles that grant 
+{kibana-ref}/xpack-security-authorization.html[access to {kib}] as well as read
+access to the <<roles-indices-priv,indices>> that will be used to generate reports.
 
 
 [[built-in-roles-snapshot-user]] `snapshot_user`::
 [[built-in-roles-snapshot-user]] `snapshot_user`::
 Grants the necessary privileges to create snapshots of **all** the indices and
 Grants the necessary privileges to create snapshots of **all** the indices and

+ 3 - 2
x-pack/docs/en/security/ccs-clients-integrations/cross-cluster-kibana.asciidoc

@@ -31,8 +31,9 @@ NOTE: If you configure the local cluster as another remote in {es}, the
 `logstash_reader` role on your local cluster also needs to grant the
 `logstash_reader` role on your local cluster also needs to grant the
 `read_cross_cluster` privilege.
 `read_cross_cluster` privilege.
 
 
-. Assign your {kib} users the `kibana_user` role and your `logstash_reader`
-role.
+. Assign your {kib} users a role that grants
+{kibana-ref}/xpack-security-authorization.html[access to {kib}]
+as well as your `logstash_reader` role.
 
 
 . On the remote cluster, create a `logstash_reader` role that grants the
 . On the remote cluster, create a `logstash_reader` role that grants the
 `read_cross_cluster` privilege and `read` and `view_index_metadata` privileges
 `read_cross_cluster` privilege and `read` and `view_index_metadata` privileges

+ 5 - 4
x-pack/docs/en/security/get-started-security.asciidoc

@@ -168,15 +168,16 @@ Select a role to see more information about its privileges. For example, select
 the `kibana_system` role to see its list of cluster and index privileges. To
 the `kibana_system` role to see its list of cluster and index privileges. To
 learn more, see <<privileges-list-indices>>. 
 learn more, see <<privileges-list-indices>>. 
 
 
-Let's assign the `kibana_user` role to your user. Go back to the 
-*Management / Security / Users* page and select your user. Add the `kibana_user` 
+Let's assign the `kibana_admin` role to your user. Go back to the 
+*Management / Security / Users* page and select your user. Add the `kibana_admin` 
 role and save the change. For example:
 role and save the change. For example:
 
 
 [role="screenshot"]
 [role="screenshot"]
 image::security/images/assign-role.jpg["Assigning a role to a user in Kibana"]
 image::security/images/assign-role.jpg["Assigning a role to a user in Kibana"]
 
 
-This user now has access to all features in {kib}. For more information about granting
-access to Kibana see {kibana-ref}/xpack-security-authorization.html[Kibana Authorization].
+This user now has administrative access to all features in {kib}.
+For more information about granting access to Kibana see
+{kibana-ref}/xpack-security-authorization.html[Kibana authorization].
 
 
 If you completed all of the steps in 
 If you completed all of the steps in 
 {stack-gs}/get-started-elastic-stack.html[Getting started with the {stack}], you should 
 {stack-gs}/get-started-elastic-stack.html[Getting started with the {stack}], you should 

+ 14 - 6
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java

@@ -52,11 +52,9 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                 .put("superuser", SUPERUSER_ROLE_DESCRIPTOR)
                 .put("superuser", SUPERUSER_ROLE_DESCRIPTOR)
                 .put("transport_client", new RoleDescriptor("transport_client", new String[] { "transport_client" }, null, null,
                 .put("transport_client", new RoleDescriptor("transport_client", new String[] { "transport_client" }, null, null,
                         MetadataUtils.DEFAULT_RESERVED_METADATA))
                         MetadataUtils.DEFAULT_RESERVED_METADATA))
-                .put("kibana_user", new RoleDescriptor("kibana_user", null, null, new RoleDescriptor.ApplicationResourcePrivileges[] {
-                        RoleDescriptor.ApplicationResourcePrivileges.builder()
-                            .application("kibana-.kibana").resources("*").privileges("all").build() },
-                        null, null,
-                        MetadataUtils.DEFAULT_RESERVED_METADATA, null))
+                .put("kibana_admin", kibanaAdminUser("kibana_admin", MetadataUtils.DEFAULT_RESERVED_METADATA))
+                .put("kibana_user", kibanaAdminUser("kibana_user",
+                    MetadataUtils.getDeprecatedReservedMetadata("Please use the [kibana_admin] role instead")))
                 .put("monitoring_user", new RoleDescriptor("monitoring_user",
                 .put("monitoring_user", new RoleDescriptor("monitoring_user",
                         new String[] { "cluster:monitor/main", "cluster:monitor/xpack/info", RemoteInfoAction.NAME },
                         new String[] { "cluster:monitor/main", "cluster:monitor/xpack/info", RemoteInfoAction.NAME },
                         new RoleDescriptor.IndicesPrivileges[] {
                         new RoleDescriptor.IndicesPrivileges[] {
@@ -110,7 +108,7 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                             RoleDescriptor.ApplicationResourcePrivileges.builder()
                             RoleDescriptor.ApplicationResourcePrivileges.builder()
                             .application("kibana-.kibana").resources("*").privileges("read").build() },
                             .application("kibana-.kibana").resources("*").privileges("read").build() },
                         null, null,
                         null, null,
-                        MetadataUtils.DEFAULT_RESERVED_METADATA,
+                        MetadataUtils.getDeprecatedReservedMetadata("Please use Kibana feature privileges instead"),
                         null))
                         null))
                 .put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
                 .put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
                         new String[] {
                         new String[] {
@@ -266,6 +264,16 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                 .immutableMap();
                 .immutableMap();
     }
     }
 
 
+    private static RoleDescriptor kibanaAdminUser(String name, Map<String, Object> metadata) {
+        return new RoleDescriptor(name, null, null,
+            new RoleDescriptor.ApplicationResourcePrivileges[] {
+                RoleDescriptor.ApplicationResourcePrivileges.builder()
+                    .application("kibana-.kibana")
+                    .resources("*").privileges("all")
+                    .build() },
+            null, null, metadata, null);
+    }
+
     public static boolean isReserved(String role) {
     public static boolean isReserved(String role) {
         return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) || UsernamesField.XPACK_ROLE.equals(role);
         return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) || UsernamesField.XPACK_ROLE.equals(role);
     }
     }

+ 10 - 0
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/MetadataUtils.java

@@ -11,6 +11,8 @@ public class MetadataUtils {
 
 
     public static final String RESERVED_PREFIX = "_";
     public static final String RESERVED_PREFIX = "_";
     public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
     public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
+    public static final String DEPRECATED_METADATA_KEY = RESERVED_PREFIX + "deprecated";
+    public static final String DEPRECATED_REASON_METADATA_KEY = RESERVED_PREFIX + "deprecated_reason";
     public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Map.of(RESERVED_METADATA_KEY, true);
     public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Map.of(RESERVED_METADATA_KEY, true);
 
 
     private MetadataUtils() {
     private MetadataUtils() {
@@ -24,4 +26,12 @@ public class MetadataUtils {
         }
         }
         return false;
         return false;
     }
     }
+
+    public static Map<String, Object> getDeprecatedReservedMetadata(String reason) {
+        return Map.of(
+            RESERVED_METADATA_KEY, true,
+            DEPRECATED_METADATA_KEY, true,
+            DEPRECATED_REASON_METADATA_KEY, reason
+        );
+    }
 }
 }

+ 52 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java

@@ -169,6 +169,7 @@ import java.util.SortedMap;
 
 
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mock;
 
 
 /**
 /**
@@ -184,6 +185,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(ReservedRolesStore.isReserved("foobar"), is(false));
         assertThat(ReservedRolesStore.isReserved("foobar"), is(false));
         assertThat(ReservedRolesStore.isReserved(SystemUser.ROLE_NAME), is(true));
         assertThat(ReservedRolesStore.isReserved(SystemUser.ROLE_NAME), is(true));
         assertThat(ReservedRolesStore.isReserved("transport_client"), is(true));
         assertThat(ReservedRolesStore.isReserved("transport_client"), is(true));
+        assertThat(ReservedRolesStore.isReserved("kibana_admin"), is(true));
         assertThat(ReservedRolesStore.isReserved("kibana_user"), is(true));
         assertThat(ReservedRolesStore.isReserved("kibana_user"), is(true));
         assertThat(ReservedRolesStore.isReserved("ingest_admin"), is(true));
         assertThat(ReservedRolesStore.isReserved("ingest_admin"), is(true));
         assertThat(ReservedRolesStore.isReserved("monitoring_user"), is(true));
         assertThat(ReservedRolesStore.isReserved("monitoring_user"), is(true));
@@ -409,6 +411,54 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNoAccessAllowed(kibanaRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
         assertNoAccessAllowed(kibanaRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
     }
     }
 
 
+    public void testKibanaAdminRole() {
+        final TransportRequest request = mock(TransportRequest.class);
+        final Authentication authentication = mock(Authentication.class);
+
+        RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_admin");
+        assertNotNull(roleDescriptor);
+        assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
+        assertThat(roleDescriptor.getMetadata(), not(hasEntry("_deprecated", true)));
+
+        Role kibanaAdminRole = Role.builder(roleDescriptor, null).build();
+        assertThat(kibanaAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
+        assertThat(kibanaAdminRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
+        assertThat(kibanaAdminRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
+        assertThat(kibanaAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
+        assertThat(kibanaAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));
+        assertThat(kibanaAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication),
+                is(false));
+        assertThat(kibanaAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false));
+        assertThat(kibanaAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication),
+                is(false));
+
+        assertThat(kibanaAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
+
+        assertThat(kibanaAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
+        assertThat(kibanaAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false));
+        assertThat(
+                kibanaAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)),
+                is(false));
+
+        final String randomApplication = "kibana-" + randomAlphaOfLengthBetween(8, 24);
+        assertThat(kibanaAdminRole.application().grants(new ApplicationPrivilege(randomApplication, "app-random", "all"),
+                "*"), is(false));
+
+        final String application = "kibana-.kibana";
+        assertThat(kibanaAdminRole.application().grants(new ApplicationPrivilege(application, "app-foo", "foo"), "*"),
+                is(false));
+        assertThat(kibanaAdminRole.application().grants(new ApplicationPrivilege(application, "app-all", "all"), "*"),
+                is(true));
+
+        final String applicationWithRandomIndex = "kibana-.kibana_" + randomAlphaOfLengthBetween(8, 24);
+        assertThat(
+                kibanaAdminRole.application()
+                        .grants(new ApplicationPrivilege(applicationWithRandomIndex, "app-random-index", "all"), "*"),
+                is(false));
+
+        assertNoAccessAllowed(kibanaAdminRole, RestrictedIndicesNames.RESTRICTED_NAMES);
+    }
+
     public void testKibanaUserRole() {
     public void testKibanaUserRole() {
         final TransportRequest request = mock(TransportRequest.class);
         final TransportRequest request = mock(TransportRequest.class);
         final Authentication authentication = mock(Authentication.class);
         final Authentication authentication = mock(Authentication.class);
@@ -416,6 +466,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user");
         RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user");
         assertNotNull(roleDescriptor);
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
+        assertThat(roleDescriptor.getMetadata(), hasEntry("_deprecated", true));
 
 
         Role kibanaUserRole = Role.builder(roleDescriptor, null).build();
         Role kibanaUserRole = Role.builder(roleDescriptor, null).build();
         assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
@@ -745,6 +796,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_dashboard_only_user");
         RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_dashboard_only_user");
         assertNotNull(roleDescriptor);
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
+        assertThat(roleDescriptor.getMetadata(), hasEntry("_deprecated", true));
 
 
         Role dashboardsOnlyUserRole = Role.builder(roleDescriptor, null).build();
         Role dashboardsOnlyUserRole = Role.builder(roleDescriptor, null).build();
         assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));

+ 15 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java

@@ -17,6 +17,7 @@ import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.cache.Cache;
 import org.elasticsearch.common.cache.Cache;
 import org.elasticsearch.common.cache.CacheBuilder;
 import org.elasticsearch.common.cache.CacheBuilder;
 import org.elasticsearch.common.collect.Tuple;
 import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting;
 import org.elasticsearch.common.settings.Setting.Property;
 import org.elasticsearch.common.settings.Setting.Property;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
@@ -41,6 +42,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
 import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
 import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
 import org.elasticsearch.xpack.core.security.support.CacheIteratorHelper;
 import org.elasticsearch.xpack.core.security.support.CacheIteratorHelper;
+import org.elasticsearch.xpack.core.security.support.MetadataUtils;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.security.user.User;
@@ -83,6 +85,7 @@ public class CompositeRolesStore {
         Setting.intSetting("xpack.security.authz.store.roles.negative_lookup_cache.max_size", 10000, Property.NodeScope);
         Setting.intSetting("xpack.security.authz.store.roles.negative_lookup_cache.max_size", 10000, Property.NodeScope);
     private static final Logger logger = LogManager.getLogger(CompositeRolesStore.class);
     private static final Logger logger = LogManager.getLogger(CompositeRolesStore.class);
 
 
+    private final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
 
 
     private final FileRolesStore fileRolesStore;
     private final FileRolesStore fileRolesStore;
     private final NativeRolesStore nativeRolesStore;
     private final NativeRolesStore nativeRolesStore;
@@ -154,6 +157,7 @@ public class CompositeRolesStore {
             final long invalidationCounter = numInvalidation.get();
             final long invalidationCounter = numInvalidation.get();
             roleDescriptors(roleNames, ActionListener.wrap(
             roleDescriptors(roleNames, ActionListener.wrap(
                     rolesRetrievalResult -> {
                     rolesRetrievalResult -> {
+                        logDeprecatedRoles(rolesRetrievalResult.roleDescriptors);
                         final boolean missingRoles = rolesRetrievalResult.getMissingRoles().isEmpty() == false;
                         final boolean missingRoles = rolesRetrievalResult.getMissingRoles().isEmpty() == false;
                         if (missingRoles) {
                         if (missingRoles) {
                             logger.debug(() -> new ParameterizedMessage("Could not find roles with names {}",
                             logger.debug(() -> new ParameterizedMessage("Could not find roles with names {}",
@@ -179,6 +183,17 @@ public class CompositeRolesStore {
         }
         }
     }
     }
 
 
+    void logDeprecatedRoles(Set<RoleDescriptor> roleDescriptors) {
+        roleDescriptors.stream()
+            .filter(rd -> Boolean.TRUE.equals(rd.getMetadata().get(MetadataUtils.DEPRECATED_METADATA_KEY)))
+            .forEach(rd -> {
+                String reason = Objects.toString(
+                    rd.getMetadata().get(MetadataUtils.DEPRECATED_REASON_METADATA_KEY), "Please check the documentation");
+                deprecationLogger.deprecatedAndMaybeLog("deprecated_role-" + rd.getName(), "The role [" + rd.getName() +
+                    "] is deprecated and will be removed in a future version of Elasticsearch. " + reason);
+            });
+    }
+
     public void getRoles(User user, Authentication authentication, ActionListener<Role> roleActionListener) {
     public void getRoles(User user, Authentication authentication, ActionListener<Role> roleActionListener) {
         // we need to special case the internal users in this method, if we apply the anonymous roles to every user including these system
         // we need to special case the internal users in this method, if we apply the anonymous roles to every user including these system
         // user accounts then we run into the chance of a deadlock because then we need to get a role that we may be trying to get as the
         // user accounts then we run into the chance of a deadlock because then we need to get a role that we may be trying to get as the

+ 129 - 44
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java

@@ -15,6 +15,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.metadata.IndexMetaData;
 import org.elasticsearch.cluster.metadata.MetaData;
 import org.elasticsearch.cluster.metadata.MetaData;
+import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
@@ -53,6 +54,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
 import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
 import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
 import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
 import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
+import org.elasticsearch.xpack.core.security.support.MetadataUtils;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.security.user.User;
@@ -64,10 +66,14 @@ import org.elasticsearch.xpack.security.support.SecurityIndexManager;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.time.Instant;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutionException;
@@ -84,6 +90,7 @@ import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.any;
@@ -143,21 +150,14 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }, null);
         }, null);
         FileRolesStore fileRolesStore = mock(FileRolesStore.class);
         FileRolesStore fileRolesStore = mock(FileRolesStore.class);
         doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
         doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
-        ReservedRolesStore reservedRolesStore = mock(ReservedRolesStore.class);
-        doCallRealMethod().when(reservedRolesStore).accept(any(Set.class), any(ActionListener.class));
-        NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
-        doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
 
 
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls"))).thenReturn(Collections.singleton(flsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls"))).thenReturn(Collections.singleton(flsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("dls"))).thenReturn(Collections.singleton(dlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("dls"))).thenReturn(Collections.singleton(dlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
         final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
         final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
-        final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
-        CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore,
-                reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(),
-                new ThreadContext(Settings.EMPTY), licenseState, cache, mock(ApiKeyService.class), documentSubsetBitsetCache,
-                rds -> effectiveRoleDescriptors.set(rds));
+        CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(Settings.EMPTY, fileRolesStore, null,
+            null, null, licenseState, null, null, rds -> effectiveRoleDescriptors.set(rds));
 
 
         PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
         PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
         compositeRolesStore.roles(Collections.singleton("fls"), roleFuture);
         compositeRolesStore.roles(Collections.singleton("fls"), roleFuture);
@@ -220,20 +220,13 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }, null);
         }, null);
         FileRolesStore fileRolesStore = mock(FileRolesStore.class);
         FileRolesStore fileRolesStore = mock(FileRolesStore.class);
         doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
         doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
-        ReservedRolesStore reservedRolesStore = mock(ReservedRolesStore.class);
-        doCallRealMethod().when(reservedRolesStore).accept(any(Set.class), any(ActionListener.class));
-        NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
-        doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls"))).thenReturn(Collections.singleton(flsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls"))).thenReturn(Collections.singleton(flsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("dls"))).thenReturn(Collections.singleton(dlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("dls"))).thenReturn(Collections.singleton(dlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("fls_dls"))).thenReturn(Collections.singleton(flsDlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
         when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole));
         final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
         final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
-        final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
-        CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore,
-                reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(),
-                new ThreadContext(Settings.EMPTY), licenseState, cache, mock(ApiKeyService.class), documentSubsetBitsetCache,
-                rds -> effectiveRoleDescriptors.set(rds));
+        CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(Settings.EMPTY, fileRolesStore, null,
+            null, null, licenseState, null, null, rds -> effectiveRoleDescriptors.set(rds));
 
 
         PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
         PlainActionFuture<Role> roleFuture = new PlainActionFuture<>();
         compositeRolesStore.roles(Collections.singleton("fls"), roleFuture);
         compositeRolesStore.roles(Collections.singleton("fls"), roleFuture);
@@ -266,6 +259,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
         final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
         final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
         doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
         doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
         when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet());
         when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet());
+
         doAnswer((invocationOnMock) -> {
         doAnswer((invocationOnMock) -> {
             ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
             ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
             callback.onResponse(RoleRetrievalResult.success(Collections.emptySet()));
             callback.onResponse(RoleRetrievalResult.success(Collections.emptySet()));
@@ -281,12 +275,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }).when(nativePrivilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
         }).when(nativePrivilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
 
 
         final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
         final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
-        final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
-        final CompositeRolesStore compositeRolesStore =
-                new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
-                        nativePrivilegeStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
-                        new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class),
-                        documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds));
+        final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS,
+            fileRolesStore, nativeRolesStore, reservedRolesStore, nativePrivilegeStore, null, null, null,
+            rds -> effectiveRoleDescriptors.set(rds));
         verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
         verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
 
 
         final String roleName = randomAlphaOfLengthBetween(1, 10);
         final String roleName = randomAlphaOfLengthBetween(1, 10);
@@ -322,7 +313,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
         if (getSuperuserRole && numberOfTimesToCall > 0) {
         if (getSuperuserRole && numberOfTimesToCall > 0) {
             // the superuser role was requested so we get the role descriptors again
             // the superuser role was requested so we get the role descriptors again
             verify(reservedRolesStore, times(2)).accept(anySetOf(String.class), any(ActionListener.class));
             verify(reservedRolesStore, times(2)).accept(anySetOf(String.class), any(ActionListener.class));
-            verify(nativePrivilegeStore).getPrivileges(isA(Set.class),isA(Set.class), any(ActionListener.class));
+            verify(nativePrivilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
         }
         }
         verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore, nativePrivilegeStore);
         verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore, nativePrivilegeStore);
     }
     }
@@ -422,9 +413,6 @@ public class CompositeRolesStoreTests extends ESTestCase {
         verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore);
         verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore);
     }
     }
 
 
-    private DocumentSubsetBitsetCache buildBitsetCache() {
-        return new DocumentSubsetBitsetCache(Settings.EMPTY, mock(ThreadPool.class));
-    }
 
 
     public void testCustomRolesProviders() {
     public void testCustomRolesProviders() {
         final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
         final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
@@ -899,12 +887,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
         }).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
         final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
         final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
 
 
-        final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
-        final CompositeRolesStore compositeRolesStore =
-            new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
-                mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
-                new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache,
-                rds -> {});
+        final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore,
+            nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), null, mock(ApiKeyService.class),
+            null, null);
         verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
         verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
 
 
         PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
         PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
@@ -940,11 +925,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
         }).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
         final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
         final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
 
 
-        final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
-        final CompositeRolesStore compositeRolesStore =
-            new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
-                mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(settings),
-                new XPackLicenseState(settings), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> {});
+        final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(settings, fileRolesStore, nativeRolesStore,
+            reservedRolesStore, mock(NativePrivilegeStore.class), null, mock(ApiKeyService.class), null, null);
         verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
         verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor
 
 
         PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
         PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
@@ -1124,11 +1106,9 @@ public class CompositeRolesStoreTests extends ESTestCase {
 
 
         final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
         final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache();
 
 
-        final CompositeRolesStore compositeRolesStore =
-            new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
-                mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
-                new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> {
-            });
+        final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(
+            SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, null, null, mock(ApiKeyService.class),
+            documentSubsetBitsetCache, null);
 
 
         PlainActionFuture<Map<String, Object>> usageStatsListener = new PlainActionFuture<>();
         PlainActionFuture<Map<String, Object>> usageStatsListener = new PlainActionFuture<>();
         compositeRolesStore.usageStats(usageStatsListener);
         compositeRolesStore.usageStats(usageStatsListener);
@@ -1138,6 +1118,111 @@ public class CompositeRolesStoreTests extends ESTestCase {
         assertThat(usageStats.get("dls"), is(Map.of("bit_set_cache", documentSubsetBitsetCache.usageStats())));
         assertThat(usageStats.get("dls"), is(Map.of("bit_set_cache", documentSubsetBitsetCache.usageStats())));
     }
     }
 
 
+    public void testLoggingOfDeprecatedRoles() {
+        List<RoleDescriptor> descriptors = new ArrayList<>();
+        Function<Map<String, Object>, RoleDescriptor> newRole = metadata -> new RoleDescriptor(
+            randomAlphaOfLengthBetween(4, 9), generateRandomStringArray(5, 5, false, true),
+            null, null, null, null, metadata, null);
+
+        RoleDescriptor deprecated1 = newRole.apply(MetadataUtils.getDeprecatedReservedMetadata("some reason"));
+        RoleDescriptor deprecated2 = newRole.apply(MetadataUtils.getDeprecatedReservedMetadata("a different reason"));
+
+        // Can't use getDeprecatedReservedMetadata because `Map.of` doesn't accept null values,
+        // so we clone metadata with a real value and then remove that key
+        final Map<String, Object> nullReasonMetadata = new HashMap<>(deprecated2.getMetadata());
+        nullReasonMetadata.remove(MetadataUtils.DEPRECATED_REASON_METADATA_KEY);
+        assertThat(nullReasonMetadata.keySet(), hasSize(deprecated2.getMetadata().size() -1));
+        RoleDescriptor deprecated3 = newRole.apply(nullReasonMetadata);
+
+        descriptors.add(deprecated1);
+        descriptors.add(deprecated2);
+        descriptors.add(deprecated3);
+
+        for (int i = randomIntBetween(2, 10); i > 0; i--) {
+            // the non-deprecated metadata is randomly one of:
+            // {}, {_deprecated:null}, {_deprecated:false},
+            // {_reserved:true}, {_reserved:true,_deprecated:null}, {_reserved:true,_deprecated:false}
+            Map<String, Object> metadata = randomBoolean() ? Map.of() : MetadataUtils.DEFAULT_RESERVED_METADATA;
+            if (randomBoolean()) {
+                metadata = new HashMap<>(metadata);
+                metadata.put(MetadataUtils.DEPRECATED_METADATA_KEY, randomBoolean() ? null : false);
+            }
+            descriptors.add(newRole.apply(metadata));
+        }
+        Collections.shuffle(descriptors, random());
+
+        final CompositeRolesStore compositeRolesStore =
+            buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS, null, null, null, null, null, null, null, null);
+
+        // Use a LHS so that the random-shufle-order of the list is preserved
+        compositeRolesStore.logDeprecatedRoles(new LinkedHashSet<>(descriptors));
+
+        assertWarnings(
+            "The role [" + deprecated1.getName() + "] is deprecated and will be removed in a future version of Elasticsearch." +
+                " some reason",
+            "The role [" + deprecated2.getName() + "] is deprecated and will be removed in a future version of Elasticsearch." +
+                " a different reason",
+            "The role [" + deprecated3.getName() + "] is deprecated and will be removed in a future version of Elasticsearch." +
+                " Please check the documentation"
+        );
+    }
+
+    private CompositeRolesStore buildCompositeRolesStore(Settings settings,
+                                                         @Nullable FileRolesStore fileRolesStore,
+                                                         @Nullable NativeRolesStore nativeRolesStore,
+                                                         @Nullable ReservedRolesStore reservedRolesStore,
+                                                         @Nullable NativePrivilegeStore privilegeStore,
+                                                         @Nullable XPackLicenseState licenseState,
+                                                         @Nullable ApiKeyService apiKeyService,
+                                                         @Nullable DocumentSubsetBitsetCache documentSubsetBitsetCache,
+                                                         @Nullable Consumer<Collection<RoleDescriptor>> roleConsumer) {
+        if (fileRolesStore == null) {
+            fileRolesStore = mock(FileRolesStore.class);
+            doCallRealMethod().when(fileRolesStore).accept(any(Set.class), any(ActionListener.class));
+            when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet());
+        }
+        if (nativeRolesStore == null) {
+            nativeRolesStore = mock(NativeRolesStore.class);
+            doCallRealMethod().when(nativeRolesStore).accept(any(Set.class), any(ActionListener.class));
+            doAnswer((invocationOnMock) -> {
+                ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
+                callback.onResponse(RoleRetrievalResult.failure(new RuntimeException("intentionally failed!")));
+                return null;
+            }).when(nativeRolesStore).getRoleDescriptors(isA(Set.class), any(ActionListener.class));
+        }
+        if (reservedRolesStore == null) {
+            reservedRolesStore = mock(ReservedRolesStore.class);
+            doCallRealMethod().when(reservedRolesStore).accept(any(Set.class), any(ActionListener.class));
+        }
+        if (privilegeStore == null) {
+            privilegeStore = mock(NativePrivilegeStore.class);
+            doAnswer((invocationOnMock) -> {
+                ActionListener<Collection<ApplicationPrivilegeDescriptor>> callback = null;
+                callback = (ActionListener<Collection<ApplicationPrivilegeDescriptor>>) invocationOnMock.getArguments()[2];
+                callback.onResponse(Collections.emptyList());
+                return null;
+            }).when(privilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class));
+        }
+        if (licenseState == null) {
+            licenseState = new XPackLicenseState(settings);
+        }
+        if (apiKeyService == null) {
+            apiKeyService = mock(ApiKeyService.class);
+        }
+        if (documentSubsetBitsetCache == null) {
+            documentSubsetBitsetCache = buildBitsetCache();
+        }
+        if (roleConsumer == null) {
+            roleConsumer = rds -> { };
+        }
+        return new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore, privilegeStore,
+            Collections.emptyList(), new ThreadContext(settings), licenseState, cache, apiKeyService, documentSubsetBitsetCache,
+            roleConsumer);
+    }
+
+    private DocumentSubsetBitsetCache buildBitsetCache() {
+        return new DocumentSubsetBitsetCache(Settings.EMPTY, mock(ThreadPool.class));
+    }
     private static class InMemoryRolesProvider implements BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>> {
     private static class InMemoryRolesProvider implements BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>> {
         private final Function<Set<String>, RoleRetrievalResult> roleDescriptorsFunc;
         private final Function<Set<String>, RoleRetrievalResult> roleDescriptorsFunc;
 
 

+ 2 - 2
x-pack/qa/oidc-op-tests/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthIT.java

@@ -276,7 +276,7 @@ public class OpenIdConnectAuthIT extends ESRestTestCase {
         final Map<String, Object> map = callAuthenticateApiUsingAccessToken(accessToken);
         final Map<String, Object> map = callAuthenticateApiUsingAccessToken(accessToken);
         logger.info("Authentication with token Response: " + map);
         logger.info("Authentication with token Response: " + map);
         assertThat(map.get("username"), equalTo("alice"));
         assertThat(map.get("username"), equalTo("alice"));
-        assertThat((List<?>) map.get("roles"), containsInAnyOrder("kibana_user", "auditor"));
+        assertThat((List<?>) map.get("roles"), containsInAnyOrder("kibana_admin", "auditor"));
 
 
         assertThat(map.get("metadata"), instanceOf(Map.class));
         assertThat(map.get("metadata"), instanceOf(Map.class));
         final Map<?, ?> metadata = (Map<?, ?>) map.get("metadata");
         final Map<?, ?> metadata = (Map<?, ?>) map.get("metadata");
@@ -374,7 +374,7 @@ public class OpenIdConnectAuthIT extends ESRestTestCase {
 
 
     private void setRoleMappings() throws IOException {
     private void setRoleMappings() throws IOException {
         Request createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_kibana");
         Request createRoleMappingRequest = new Request("PUT", "/_security/role_mapping/oidc_kibana");
-        createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"kibana_user\"]," +
+        createRoleMappingRequest.setJsonEntity("{ \"roles\" : [\"kibana_admin\"]," +
             "\"enabled\": true," +
             "\"enabled\": true," +
             "\"rules\": {" +
             "\"rules\": {" +
             "\"field\": { \"realm.name\": \"" + REALM_NAME + "\"}" +
             "\"field\": { \"realm.name\": \"" + REALM_NAME + "\"}" +