Skip to content

Commit 94e0dc8

Browse files
author
David Roberts
committed
Add ML admin permissions to the kibana_system role
As part of the "ML in Spaces" project, access to the ML UI in Kibana is migrating to being controlled by Kibana privileges. The ML UI will check whether the logged-in user has permission to do something ML-related using Kibana privileges, and if they do will call the relevant ML Elasticsearch API using the Kibana system user. In order for this to work the kibana_system role needs to have administrative access to ML. Backport of elastic#58061
1 parent 1e235a7 commit 94e0dc8

File tree

2 files changed

+93
-27
lines changed

2 files changed

+93
-27
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
117117
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
118118
InvalidateApiKeyAction.NAME, "grant_api_key",
119119
GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME,
120+
// To facilitate ML UI functionality being controlled using Kibana security privileges
121+
"manage_ml",
120122
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
121123
"cluster:admin/analyze"
122124
},
@@ -127,6 +129,12 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
127129
.indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
128130
RoleDescriptor.IndicesPrivileges.builder()
129131
.indices(".management-beats").privileges("create_index", "read", "write").build(),
132+
// To facilitate ML UI functionality being controlled using Kibana security privileges
133+
RoleDescriptor.IndicesPrivileges.builder()
134+
.indices(".ml-anomalies*", ".ml-notifications*", ".ml-stats-*")
135+
.privileges("read").build(),
136+
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*")
137+
.privileges("read", "write").build(),
130138
// APM agent configuration
131139
RoleDescriptor.IndicesPrivileges.builder()
132140
.indices(".apm-agent-configuration").privileges("all").build(),
@@ -181,6 +189,7 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
181189
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*")
182190
.privileges("view_index_metadata", "read", "write").build()
183191
},
192+
// TODO: remove Kibana privileges from ML backend roles in 8.0.0
184193
new RoleDescriptor.ApplicationResourcePrivileges[] {
185194
RoleDescriptor.ApplicationResourcePrivileges.builder()
186195
.application("kibana-*").resources("*").privileges("reserved_ml_user").build()
@@ -194,6 +203,7 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
194203
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*")
195204
.privileges("view_index_metadata", "read", "write").build()
196205
},
206+
// TODO: remove Kibana privileges from ML backend roles in 8.0.0
197207
new RoleDescriptor.ApplicationResourcePrivileges[] {
198208
RoleDescriptor.ApplicationResourcePrivileges.builder()
199209
.application("kibana-*").resources("*").privileges("reserved_ml_admin").build()

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

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
import org.elasticsearch.xpack.core.ml.action.DeleteForecastAction;
6767
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
6868
import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction;
69+
import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAction;
70+
import org.elasticsearch.xpack.core.ml.action.EstimateModelMemoryAction;
71+
import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction;
72+
import org.elasticsearch.xpack.core.ml.action.ExplainDataFrameAnalyticsAction;
6973
import org.elasticsearch.xpack.core.ml.action.FinalizeJobExecutionAction;
7074
import org.elasticsearch.xpack.core.ml.action.FindFileStructureAction;
7175
import org.elasticsearch.xpack.core.ml.action.FlushJobAction;
@@ -74,6 +78,8 @@
7478
import org.elasticsearch.xpack.core.ml.action.GetCalendarEventsAction;
7579
import org.elasticsearch.xpack.core.ml.action.GetCalendarsAction;
7680
import org.elasticsearch.xpack.core.ml.action.GetCategoriesAction;
81+
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsAction;
82+
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsStatsAction;
7783
import org.elasticsearch.xpack.core.ml.action.GetDatafeedsAction;
7884
import org.elasticsearch.xpack.core.ml.action.GetDatafeedsStatsAction;
7985
import org.elasticsearch.xpack.core.ml.action.GetFiltersAction;
@@ -83,6 +89,9 @@
8389
import org.elasticsearch.xpack.core.ml.action.GetModelSnapshotsAction;
8490
import org.elasticsearch.xpack.core.ml.action.GetOverallBucketsAction;
8591
import org.elasticsearch.xpack.core.ml.action.GetRecordsAction;
92+
import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsAction;
93+
import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsStatsAction;
94+
import org.elasticsearch.xpack.core.ml.action.InternalInferModelAction;
8695
import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction;
8796
import org.elasticsearch.xpack.core.ml.action.KillProcessAction;
8897
import org.elasticsearch.xpack.core.ml.action.MlInfoAction;
@@ -92,12 +101,16 @@
92101
import org.elasticsearch.xpack.core.ml.action.PostDataAction;
93102
import org.elasticsearch.xpack.core.ml.action.PreviewDatafeedAction;
94103
import org.elasticsearch.xpack.core.ml.action.PutCalendarAction;
104+
import org.elasticsearch.xpack.core.ml.action.PutDataFrameAnalyticsAction;
95105
import org.elasticsearch.xpack.core.ml.action.PutDatafeedAction;
96106
import org.elasticsearch.xpack.core.ml.action.PutFilterAction;
97107
import org.elasticsearch.xpack.core.ml.action.PutJobAction;
108+
import org.elasticsearch.xpack.core.ml.action.PutTrainedModelAction;
98109
import org.elasticsearch.xpack.core.ml.action.RevertModelSnapshotAction;
99110
import org.elasticsearch.xpack.core.ml.action.SetUpgradeModeAction;
111+
import org.elasticsearch.xpack.core.ml.action.StartDataFrameAnalyticsAction;
100112
import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
113+
import org.elasticsearch.xpack.core.ml.action.StopDataFrameAnalyticsAction;
101114
import org.elasticsearch.xpack.core.ml.action.StopDatafeedAction;
102115
import org.elasticsearch.xpack.core.ml.action.UpdateCalendarJobAction;
103116
import org.elasticsearch.xpack.core.ml.action.UpdateDatafeedAction;
@@ -333,6 +346,9 @@ public void testKibanaSystemRole() {
333346
assertThat(kibanaRole.cluster().check(InvalidateApiKeyAction.NAME, request, authentication), is(true));
334347
assertThat(kibanaRole.cluster().check(GrantApiKeyAction.NAME, request, authentication), is(true));
335348

349+
// ML
350+
assertRoleHasManageMl(kibanaRole);
351+
336352
// Application Privileges
337353
DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" });
338354
DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" });
@@ -387,7 +403,7 @@ public void testKibanaSystemRole() {
387403
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(true));
388404
});
389405

390-
// read-only index access
406+
// read-only index access, including cross cluster
391407
Arrays.asList(".monitoring-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> {
392408
logger.info("index name [{}]", index);
393409
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false));
@@ -403,6 +419,26 @@ public void testKibanaSystemRole() {
403419
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(true));
404420
});
405421

422+
// read-only index access, excluding cross cluster
423+
Arrays.asList(
424+
".ml-anomalies-" + randomAlphaOfLength(randomIntBetween(0, 13)),
425+
".ml-notifications-" + randomAlphaOfLength(randomIntBetween(0, 13)),
426+
".ml-stats-" + randomAlphaOfLength(randomIntBetween(0, 13))
427+
).forEach((index) -> {
428+
logger.trace("index name [{}]", index);
429+
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false));
430+
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(false));
431+
assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false));
432+
assertThat(kibanaRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(false));
433+
assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(false));
434+
assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(false));
435+
assertThat(kibanaRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false));
436+
assertThat(kibanaRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true));
437+
assertThat(kibanaRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true));
438+
assertThat(kibanaRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true));
439+
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(false));
440+
});
441+
406442
// read-only indices for APM telemetry
407443
Arrays.asList("apm-*").forEach((index) -> {
408444
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false));
@@ -1122,6 +1158,39 @@ public void testMachineLearningAdminRole() {
11221158
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
11231159

11241160
Role role = Role.builder(roleDescriptor, null).build();
1161+
assertRoleHasManageMl(role);
1162+
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
1163+
1164+
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
1165+
1166+
assertNoAccessAllowed(role, "foo");
1167+
assertNoAccessAllowed(role, AnomalyDetectorsIndexFields.CONFIG_INDEX); // internal use only
1168+
assertOnlyReadAllowed(role, MlMetaIndex.INDEX_NAME);
1169+
assertOnlyReadAllowed(role, AnomalyDetectorsIndexFields.STATE_INDEX_PREFIX);
1170+
assertOnlyReadAllowed(role, AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX + AnomalyDetectorsIndexFields.RESULTS_INDEX_DEFAULT);
1171+
assertOnlyReadAllowed(role, NotificationsIndex.NOTIFICATIONS_INDEX);
1172+
assertReadWriteDocsButNotDeleteIndexAllowed(role, AnnotationIndex.INDEX_NAME);
1173+
1174+
assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
1175+
assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
1176+
1177+
final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
1178+
assertThat(role.application().grants(
1179+
new ApplicationPrivilege(kibanaApplicationWithRandomIndex, "app-foo", "foo"), "*"), is(false));
1180+
assertThat(role.application().grants(
1181+
new ApplicationPrivilege(kibanaApplicationWithRandomIndex, "app-reserved_ml", "reserved_ml_admin"), "*"), is(true));
1182+
1183+
final String otherApplication = "logstash-" + randomAlphaOfLengthBetween(8, 24);
1184+
assertThat(role.application().grants(
1185+
new ApplicationPrivilege(otherApplication, "app-foo", "foo"), "*"), is(false));
1186+
assertThat(role.application().grants(
1187+
new ApplicationPrivilege(otherApplication, "app-reserved_ml", "reserved_ml_admin"), "*"), is(false));
1188+
}
1189+
1190+
private void assertRoleHasManageMl(Role role) {
1191+
final TransportRequest request = mock(TransportRequest.class);
1192+
final Authentication authentication = mock(Authentication.class);
1193+
11251194
assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true));
11261195
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true));
11271196
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true));
@@ -1131,6 +1200,10 @@ public void testMachineLearningAdminRole() {
11311200
assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true));
11321201
assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true));
11331202
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true));
1203+
assertThat(role.cluster().check(DeleteTrainedModelAction.NAME, request, authentication), is(true));
1204+
assertThat(role.cluster().check(EstimateModelMemoryAction.NAME, request, authentication), is(true));
1205+
assertThat(role.cluster().check(EvaluateDataFrameAction.NAME, request, authentication), is(true));
1206+
assertThat(role.cluster().check(ExplainDataFrameAnalyticsAction.NAME, request, authentication), is(true));
11341207
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only
11351208
assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true));
11361209
assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true));
@@ -1141,13 +1214,18 @@ public void testMachineLearningAdminRole() {
11411214
assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true));
11421215
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true));
11431216
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true));
1217+
assertThat(role.cluster().check(GetDataFrameAnalyticsAction.NAME, request, authentication), is(true));
1218+
assertThat(role.cluster().check(GetDataFrameAnalyticsStatsAction.NAME, request, authentication), is(true));
11441219
assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true));
11451220
assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true));
11461221
assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true));
11471222
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true));
11481223
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true));
11491224
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true));
11501225
assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true));
1226+
assertThat(role.cluster().check(GetTrainedModelsAction.NAME, request, authentication), is(true));
1227+
assertThat(role.cluster().check(GetTrainedModelsStatsAction.NAME, request, authentication), is(true));
1228+
assertThat(role.cluster().check(InternalInferModelAction.NAME, request, authentication), is(false)); // internal use only
11511229
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only
11521230
assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only
11531231
assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true));
@@ -1158,12 +1236,16 @@ public void testMachineLearningAdminRole() {
11581236
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true));
11591237
assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true));
11601238
assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true));
1239+
assertThat(role.cluster().check(PutDataFrameAnalyticsAction.NAME, request, authentication), is(true));
11611240
assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true));
11621241
assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true));
1242+
assertThat(role.cluster().check(PutTrainedModelAction.NAME, request, authentication), is(true));
11631243
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true));
11641244
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true));
11651245
assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true));
1246+
assertThat(role.cluster().check(StartDataFrameAnalyticsAction.NAME, request, authentication), is(true));
11661247
assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true));
1248+
assertThat(role.cluster().check(StopDataFrameAnalyticsAction.NAME, request, authentication), is(true));
11671249
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true));
11681250
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true));
11691251
assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true));
@@ -1172,32 +1254,6 @@ public void testMachineLearningAdminRole() {
11721254
assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only
11731255
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true));
11741256
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true));
1175-
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
1176-
1177-
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
1178-
1179-
assertNoAccessAllowed(role, "foo");
1180-
assertNoAccessAllowed(role, AnomalyDetectorsIndexFields.CONFIG_INDEX); // internal use only
1181-
assertOnlyReadAllowed(role, MlMetaIndex.INDEX_NAME);
1182-
assertOnlyReadAllowed(role, AnomalyDetectorsIndexFields.STATE_INDEX_PREFIX);
1183-
assertOnlyReadAllowed(role, AnomalyDetectorsIndexFields.RESULTS_INDEX_PREFIX + AnomalyDetectorsIndexFields.RESULTS_INDEX_DEFAULT);
1184-
assertOnlyReadAllowed(role, NotificationsIndex.NOTIFICATIONS_INDEX);
1185-
assertReadWriteDocsButNotDeleteIndexAllowed(role, AnnotationIndex.INDEX_NAME);
1186-
1187-
assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
1188-
assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
1189-
1190-
final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
1191-
assertThat(role.application().grants(
1192-
new ApplicationPrivilege(kibanaApplicationWithRandomIndex, "app-foo", "foo"), "*"), is(false));
1193-
assertThat(role.application().grants(
1194-
new ApplicationPrivilege(kibanaApplicationWithRandomIndex, "app-reserved_ml", "reserved_ml_admin"), "*"), is(true));
1195-
1196-
final String otherApplication = "logstash-" + randomAlphaOfLengthBetween(8, 24);
1197-
assertThat(role.application().grants(
1198-
new ApplicationPrivilege(otherApplication, "app-foo", "foo"), "*"), is(false));
1199-
assertThat(role.application().grants(
1200-
new ApplicationPrivilege(otherApplication, "app-reserved_ml", "reserved_ml_admin"), "*"), is(false));
12011257
}
12021258

12031259
public void testMachineLearningUserRole() {

0 commit comments

Comments
 (0)