Add ML admin permissions to the kibana_system role (#58172)

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 #58061
This commit is contained in:
David Roberts 2020-06-17 17:03:32 +01:00 committed by GitHub
parent 4962a91157
commit 3f8d16304c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 51 deletions

View File

@ -117,6 +117,8 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
InvalidateApiKeyAction.NAME, "grant_api_key",
GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME,
// To facilitate ML UI functionality being controlled using Kibana security privileges
"manage_ml",
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
"cluster:admin/analyze"
},
@ -127,6 +129,12 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
.indices(".monitoring-*").privileges("read", "read_cross_cluster").build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices(".management-beats").privileges("create_index", "read", "write").build(),
// To facilitate ML UI functionality being controlled using Kibana security privileges
RoleDescriptor.IndicesPrivileges.builder()
.indices(".ml-anomalies*", ".ml-notifications*", ".ml-stats-*")
.privileges("read").build(),
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*")
.privileges("read", "write").build(),
// APM agent configuration
RoleDescriptor.IndicesPrivileges.builder()
.indices(".apm-agent-configuration").privileges("all").build(),
@ -181,6 +189,7 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*")
.privileges("view_index_metadata", "read", "write").build()
},
// TODO: remove Kibana privileges from ML backend roles in 8.0.0
new RoleDescriptor.ApplicationResourcePrivileges[] {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana-*").resources("*").privileges("reserved_ml_user").build()
@ -194,6 +203,7 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*")
.privileges("view_index_metadata", "read", "write").build()
},
// TODO: remove Kibana privileges from ML backend roles in 8.0.0
new RoleDescriptor.ApplicationResourcePrivileges[] {
RoleDescriptor.ApplicationResourcePrivileges.builder()
.application("kibana-*").resources("*").privileges("reserved_ml_admin").build()

View File

@ -66,6 +66,10 @@ import org.elasticsearch.xpack.core.ml.action.DeleteFilterAction;
import org.elasticsearch.xpack.core.ml.action.DeleteForecastAction;
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction;
import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAction;
import org.elasticsearch.xpack.core.ml.action.EstimateModelMemoryAction;
import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction;
import org.elasticsearch.xpack.core.ml.action.ExplainDataFrameAnalyticsAction;
import org.elasticsearch.xpack.core.ml.action.FinalizeJobExecutionAction;
import org.elasticsearch.xpack.core.ml.action.FindFileStructureAction;
import org.elasticsearch.xpack.core.ml.action.FlushJobAction;
@ -74,6 +78,8 @@ import org.elasticsearch.xpack.core.ml.action.GetBucketsAction;
import org.elasticsearch.xpack.core.ml.action.GetCalendarEventsAction;
import org.elasticsearch.xpack.core.ml.action.GetCalendarsAction;
import org.elasticsearch.xpack.core.ml.action.GetCategoriesAction;
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsAction;
import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsStatsAction;
import org.elasticsearch.xpack.core.ml.action.GetDatafeedsAction;
import org.elasticsearch.xpack.core.ml.action.GetDatafeedsStatsAction;
import org.elasticsearch.xpack.core.ml.action.GetFiltersAction;
@ -83,6 +89,9 @@ import org.elasticsearch.xpack.core.ml.action.GetJobsStatsAction;
import org.elasticsearch.xpack.core.ml.action.GetModelSnapshotsAction;
import org.elasticsearch.xpack.core.ml.action.GetOverallBucketsAction;
import org.elasticsearch.xpack.core.ml.action.GetRecordsAction;
import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsAction;
import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsStatsAction;
import org.elasticsearch.xpack.core.ml.action.InternalInferModelAction;
import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction;
import org.elasticsearch.xpack.core.ml.action.KillProcessAction;
import org.elasticsearch.xpack.core.ml.action.MlInfoAction;
@ -92,12 +101,16 @@ import org.elasticsearch.xpack.core.ml.action.PostCalendarEventsAction;
import org.elasticsearch.xpack.core.ml.action.PostDataAction;
import org.elasticsearch.xpack.core.ml.action.PreviewDatafeedAction;
import org.elasticsearch.xpack.core.ml.action.PutCalendarAction;
import org.elasticsearch.xpack.core.ml.action.PutDataFrameAnalyticsAction;
import org.elasticsearch.xpack.core.ml.action.PutDatafeedAction;
import org.elasticsearch.xpack.core.ml.action.PutFilterAction;
import org.elasticsearch.xpack.core.ml.action.PutJobAction;
import org.elasticsearch.xpack.core.ml.action.PutTrainedModelAction;
import org.elasticsearch.xpack.core.ml.action.RevertModelSnapshotAction;
import org.elasticsearch.xpack.core.ml.action.SetUpgradeModeAction;
import org.elasticsearch.xpack.core.ml.action.StartDataFrameAnalyticsAction;
import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
import org.elasticsearch.xpack.core.ml.action.StopDataFrameAnalyticsAction;
import org.elasticsearch.xpack.core.ml.action.StopDatafeedAction;
import org.elasticsearch.xpack.core.ml.action.UpdateCalendarJobAction;
import org.elasticsearch.xpack.core.ml.action.UpdateDatafeedAction;
@ -333,6 +346,9 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(kibanaRole.cluster().check(InvalidateApiKeyAction.NAME, request, authentication), is(true));
assertThat(kibanaRole.cluster().check(GrantApiKeyAction.NAME, request, authentication), is(true));
// ML
assertRoleHasManageMl(kibanaRole);
// Application Privileges
DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" });
DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" });
@ -387,7 +403,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(true));
});
// read-only index access
// read-only index access, including cross cluster
Arrays.asList(".monitoring-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> {
logger.info("index name [{}]", index);
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false));
@ -403,6 +419,26 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(true));
});
// read-only index access, excluding cross cluster
Arrays.asList(
".ml-anomalies-" + randomAlphaOfLength(randomIntBetween(0, 13)),
".ml-notifications-" + randomAlphaOfLength(randomIntBetween(0, 13)),
".ml-stats-" + randomAlphaOfLength(randomIntBetween(0, 13))
).forEach((index) -> {
logger.trace("index name [{}]", index);
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true));
assertThat(kibanaRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true));
assertThat(kibanaRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true));
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(false));
});
// read-only indices for APM telemetry
Arrays.asList("apm-*").forEach((index) -> {
assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false));
@ -1122,56 +1158,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true));
assertRoleHasManageMl(role);
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -1200,6 +1187,75 @@ public class ReservedRolesStoreTests extends ESTestCase {
new ApplicationPrivilege(otherApplication, "app-reserved_ml", "reserved_ml_admin"), "*"), is(false));
}
private void assertRoleHasManageMl(Role role) {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);
assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(DeleteTrainedModelAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(EstimateModelMemoryAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(EvaluateDataFrameAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ExplainDataFrameAnalyticsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDataFrameAnalyticsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetDataFrameAnalyticsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetTrainedModelsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(GetTrainedModelsStatsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(InternalInferModelAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutDataFrameAnalyticsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(PutTrainedModelAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StartDataFrameAnalyticsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(StopDataFrameAnalyticsAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true));
}
public void testMachineLearningUserRole() {
final TransportRequest request = mock(TransportRequest.class);
final Authentication authentication = mock(Authentication.class);