From 30cfb12b7dc91c147567e4ca66b635f012df4a01 Mon Sep 17 00:00:00 2001 From: Benjamin Trent <4357155+benwtrent@users.noreply.github.com> Date: Mon, 23 Mar 2020 12:19:20 -0400 Subject: [PATCH 1/6] [ML] prefer secondary authorization header for data[feed|frame] authz --- .../apis/preview-datafeed.asciidoc | 6 +++ .../apis/put-datafeed.asciidoc | 3 ++ .../apis/update-datafeed.asciidoc | 6 +++ .../apis/put-dfanalytics.asciidoc | 6 +++ .../xpack/core/ClientHelper.java | 4 +- .../support/SecondaryAuthentication.java | 2 +- .../ml/integration/DatafeedJobsRestIT.java | 49 +++++++++++++++++++ .../TransportPreviewDatafeedAction.java | 7 ++- .../TransportPutDataFrameAnalyticsAction.java | 26 +++++++--- .../ml/action/TransportPutDatafeedAction.java | 28 +++++++++-- .../action/TransportUpdateDatafeedAction.java | 5 +- .../xpack/ml/utils/AuthHeadersExtractor.java | 38 ++++++++++++++ 12 files changed, 162 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java diff --git a/docs/reference/ml/anomaly-detection/apis/preview-datafeed.asciidoc b/docs/reference/ml/anomaly-detection/apis/preview-datafeed.asciidoc index 6220d8a1de242..acbd425928bc1 100644 --- a/docs/reference/ml/anomaly-detection/apis/preview-datafeed.asciidoc +++ b/docs/reference/ml/anomaly-detection/apis/preview-datafeed.asciidoc @@ -36,6 +36,12 @@ to create or update it. If the two sets of roles differ then the preview may not accurately reflect what the {dfeed} will return when started. To avoid such problems, the same user that creates/updates the {dfeed} should preview it to ensure it is returning the expected data. ++ +-- +NOTE: It is possible that secondary authorization headers are supplied in the +request. If this is the case, the secondary authorization headers are used +instead of the primary headers. +-- [[ml-preview-datafeed-path-parms]] ==== {api-path-parms-title} diff --git a/docs/reference/ml/anomaly-detection/apis/put-datafeed.asciidoc b/docs/reference/ml/anomaly-detection/apis/put-datafeed.asciidoc index 86bc85c34148f..8355ec9cb32bd 100644 --- a/docs/reference/ml/anomaly-detection/apis/put-datafeed.asciidoc +++ b/docs/reference/ml/anomaly-detection/apis/put-datafeed.asciidoc @@ -42,6 +42,9 @@ each interval. See {ml-docs}/ml-delayed-data-detection.html[Handling delayed dat * When {es} {security-features} are enabled, your {dfeed} remembers which roles the user who created it had at the time of creation and runs the query using those same roles. +* It is possible that secondary authorization headers are supplied in the + request. If this is the case, the secondary authorization headers are used + instead of the primary headers. ==== [[ml-put-datafeed-path-parms]] diff --git a/docs/reference/ml/anomaly-detection/apis/update-datafeed.asciidoc b/docs/reference/ml/anomaly-detection/apis/update-datafeed.asciidoc index 3a6d42ee00484..e24115190195e 100644 --- a/docs/reference/ml/anomaly-detection/apis/update-datafeed.asciidoc +++ b/docs/reference/ml/anomaly-detection/apis/update-datafeed.asciidoc @@ -35,6 +35,12 @@ IMPORTANT: When {es} {security-features} are enabled, your {dfeed} remembers which roles the user who updated it had at the time of update and runs the query using those same roles. ++ +-- +NOTE: It is possible that secondary authorization headers are supplied in the +request. If this is the case, the secondary authorization headers are used +instead of the primary headers. +-- [[ml-update-datafeed-path-parms]] ==== {api-path-parms-title} diff --git a/docs/reference/ml/df-analytics/apis/put-dfanalytics.asciidoc b/docs/reference/ml/df-analytics/apis/put-dfanalytics.asciidoc index 6a011dcb04f48..5639e8f4a7e4f 100644 --- a/docs/reference/ml/df-analytics/apis/put-dfanalytics.asciidoc +++ b/docs/reference/ml/df-analytics/apis/put-dfanalytics.asciidoc @@ -33,6 +33,12 @@ built-in roles and privileges: For more information, see <> and <>. ++ +-- +NOTE: It is possible that secondary authorization headers are supplied in the +request. If this is the case, the secondary authorization headers are used +instead of the primary headers. +-- [[ml-put-dfanalytics-desc]] ==== {api-description-title} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java index ed03ab9ffc058..00a1d5e07d581 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField; +import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; import java.util.Map; import java.util.Set; @@ -34,7 +35,8 @@ public final class ClientHelper { * List of headers that are related to security */ public static final Set SECURITY_HEADER_FILTERS = Sets.newHashSet(AuthenticationServiceField.RUN_AS_USER_HEADER, - AuthenticationField.AUTHENTICATION_KEY); + AuthenticationField.AUTHENTICATION_KEY, + SecondaryAuthentication.THREAD_CTX_KEY); /** * . diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java index 8f032f480e07f..c7e3165e515cc 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java @@ -24,7 +24,7 @@ */ public class SecondaryAuthentication { - private static final String THREAD_CTX_KEY = "_xpack_security_secondary_authc"; + public static final String THREAD_CTX_KEY = "_xpack_security_secondary_authc"; private final SecurityContext securityContext; private final Authentication authentication; diff --git a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java index f609b19081e6b..0899e4a8729e9 100644 --- a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java +++ b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java @@ -544,6 +544,46 @@ public void testInsufficientSearchPrivilegesOnPreview() throws Exception { containsString("[indices:data/read/field_caps] is unauthorized for user [ml_admin]")); } + public void testSecondaryAuthSearchPrivilegesLookBack() throws Exception { + setupDataAccessRole("airline-data"); + String jobId = "secondary-privs-put-job"; + createJob(jobId, "airline.keyword"); + String datafeedId = "datafeed-" + jobId; + // Primary auth header does not have accesss, but secondary auth does + new DatafeedBuilder(datafeedId, jobId, "airline-data") + .setAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN) + .setSecondaryAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN_WITH_SOME_DATA_ACCESS) + .build(); + openJob(client(), jobId); + + startDatafeedAndWaitUntilStopped(datafeedId); + waitUntilJobIsClosed(jobId); + + Response jobStatsResponse = client().performRequest(new Request("GET", + MachineLearning.BASE_PATH + "anomaly_detectors/" + jobId + "/_stats")); + String jobStatsResponseAsString = EntityUtils.toString(jobStatsResponse.getEntity()); + assertThat(jobStatsResponseAsString, containsString("\"input_record_count\":2")); + assertThat(jobStatsResponseAsString, containsString("\"processed_record_count\":2")); + assertThat(jobStatsResponseAsString, containsString("\"missing_field_count\":0")); + } + + public void testSecondaryAuthSearchPrivilegesOnPreview() throws Exception { + setupDataAccessRole("airline-data"); + String jobId = "secondary-privs-preview-job"; + createJob(jobId, "airline.keyword"); + + String datafeedId = "datafeed-" + jobId; + new DatafeedBuilder(datafeedId, jobId, "airline-data").build(); + + Request getFeed = new Request("GET", MachineLearning.BASE_PATH + "datafeeds/" + datafeedId + "/_preview"); + RequestOptions.Builder options = getFeed.getOptions().toBuilder(); + options.addHeader("Authorization", BASIC_AUTH_VALUE_ML_ADMIN); + options.addHeader("es-secondary-authorization", BASIC_AUTH_VALUE_ML_ADMIN_WITH_SOME_DATA_ACCESS); + getFeed.setOptions(options); + // Should not fail as secondary auth has permissions. + client().performRequest(getFeed); + } + public void testLookbackOnlyGivenAggregationsWithHistogram() throws Exception { String jobId = "aggs-histogram-job"; Request createJobRequest = new Request("PUT", MachineLearning.BASE_PATH + "anomaly_detectors/" + jobId); @@ -1181,6 +1221,7 @@ private static class DatafeedBuilder { String scriptedFields; String aggregations; String authHeader = BASIC_AUTH_VALUE_SUPER_USER; + String secondaryAuthHeader = null; String chunkingTimespan; String indicesOptions; @@ -1210,6 +1251,11 @@ DatafeedBuilder setAuthHeader(String authHeader) { return this; } + DatafeedBuilder setSecondaryAuthHeader(String authHeader) { + this.secondaryAuthHeader = authHeader; + return this; + } + DatafeedBuilder setChunkingTimespan(String timespan) { chunkingTimespan = timespan; return this; @@ -1233,6 +1279,9 @@ Response build() throws IOException { + "}"); RequestOptions.Builder options = request.getOptions().toBuilder(); options.addHeader("Authorization", authHeader); + if (this.secondaryAuthHeader != null) { + options.addHeader("es-secondary-authorization", secondaryAuthHeader); + } request.setOptions(options); return client().performRequest(request); } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java index e7e182b15c11d..255967d592c7e 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java @@ -15,7 +15,6 @@ import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.ml.action.PreviewDatafeedAction; import org.elasticsearch.xpack.core.ml.datafeed.ChunkingConfig; import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig; @@ -34,6 +33,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth; + public class TransportPreviewDatafeedAction extends HandledTransportAction { private final ThreadPool threadPool; @@ -65,9 +66,7 @@ protected void doExecute(Task task, PreviewDatafeedAction.Request request, Actio jobConfigProvider.getJob(datafeedConfig.getJobId(), ActionListener.wrap( jobBuilder -> { DatafeedConfig.Builder previewDatafeed = buildPreviewDatafeed(datafeedConfig); - Map headers = threadPool.getThreadContext().getHeaders().entrySet().stream() - .filter(e -> ClientHelper.SECURITY_HEADER_FILTERS.contains(e.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + Map headers = extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); previewDatafeed.setHeaders(headers); jobResultsProvider.datafeedTimingStats( jobBuilder.getId(), diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java index f2b924c1dac92..a014600bc44bd 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java @@ -57,6 +57,10 @@ import java.util.Map; import java.util.Objects; +import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; +import static org.elasticsearch.xpack.core.ClientHelper.executeWithHeadersAsync; +import static org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth; + public class TransportPutDataFrameAnalyticsAction extends TransportMasterNodeAction { @@ -140,7 +144,11 @@ private void putValidatedConfig(DataFrameAnalyticsConfig config, ActionListener< .build(); if (licenseState.isAuthAllowed()) { - final String username = securityContext.getUser().principal(); + final Map authHeaders = extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); + // If we have secondary auth, prefer it. + final String username = securityContext.getSecondaryAuthentication() == null ? + securityContext.getUser().principal() : + securityContext.getSecondaryAuthentication().getAuthentication().getUser().principal(); RoleDescriptor.IndicesPrivileges sourceIndexPrivileges = RoleDescriptor.IndicesPrivileges.builder() .indices(preparedForPutConfig.getSource().getIndex()) .privileges("read") @@ -157,10 +165,14 @@ private void putValidatedConfig(DataFrameAnalyticsConfig config, ActionListener< privRequest.indexPrivileges(sourceIndexPrivileges, destIndexPrivileges); ActionListener privResponseListener = ActionListener.wrap( - r -> handlePrivsResponse(username, preparedForPutConfig, r, listener), + r -> handlePrivsResponse(username, preparedForPutConfig, r, authHeaders, listener), listener::onFailure); - - client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener); + executeWithHeadersAsync(authHeaders, + ML_ORIGIN, + client, + HasPrivilegesAction.INSTANCE, + privRequest, + privResponseListener); } else { updateDocMappingAndPutConfig( preparedForPutConfig, @@ -172,13 +184,15 @@ private void putValidatedConfig(DataFrameAnalyticsConfig config, ActionListener< } } - private void handlePrivsResponse(String username, DataFrameAnalyticsConfig memoryCappedConfig, + private void handlePrivsResponse(String username, + DataFrameAnalyticsConfig memoryCappedConfig, HasPrivilegesResponse response, + Map authHeaders, ActionListener listener) throws IOException { if (response.isCompleteMatch()) { updateDocMappingAndPutConfig( memoryCappedConfig, - threadPool.getThreadContext().getHeaders(), + authHeaders, ActionListener.wrap( indexResponse -> listener.onResponse(new PutDataFrameAnalyticsAction.Response(memoryCappedConfig)), listener::onFailure diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java index 22fd20ddafb28..770c23d2dad7f 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java @@ -54,6 +54,7 @@ import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.ml.datafeed.persistence.DatafeedConfigProvider; import org.elasticsearch.xpack.ml.job.persistence.JobConfigProvider; +import org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor; import java.io.IOException; import java.util.Collections; @@ -61,6 +62,7 @@ import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; +import static org.elasticsearch.xpack.core.ClientHelper.executeWithHeadersAsync; public class TransportPutDatafeedAction extends TransportMasterNodeAction { @@ -109,7 +111,12 @@ protected void masterOperation(Task task, PutDatafeedAction.Request request, Clu final String[] indices = request.getDatafeed().getIndices().toArray(new String[0]); - final String username = securityContext.getUser().principal(); + // If we have secondary auth, prefer it. + final String username = securityContext.getSecondaryAuthentication() == null ? + securityContext.getUser().principal() : + securityContext.getSecondaryAuthentication().getAuthentication().getUser().principal(); + Map authHeaders = AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); + final HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]); privRequest.username(username); @@ -119,7 +126,7 @@ protected void masterOperation(Task task, PutDatafeedAction.Request request, Clu .indices(indices); ActionListener privResponseListener = ActionListener.wrap( - r -> handlePrivsResponse(username, request, r, state, listener), + r -> handlePrivsResponse(username, request, r, state, authHeaders, listener), listener::onFailure); ActionListener getRollupIndexCapsActionHandler = ActionListener.wrap( @@ -130,13 +137,23 @@ protected void masterOperation(Task task, PutDatafeedAction.Request request, Clu indicesPrivilegesBuilder.privileges(SearchAction.NAME, RollupSearchAction.NAME); } privRequest.indexPrivileges(indicesPrivilegesBuilder.build()); - client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener); + executeWithHeadersAsync(authHeaders, + ML_ORIGIN, + client, + HasPrivilegesAction.INSTANCE, + privRequest, + privResponseListener); }, e -> { if (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) { indicesPrivilegesBuilder.privileges(SearchAction.NAME); privRequest.indexPrivileges(indicesPrivilegesBuilder.build()); - client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener); + executeWithHeadersAsync(authHeaders, + ML_ORIGIN, + client, + HasPrivilegesAction.INSTANCE, + privRequest, + privResponseListener); } else { listener.onFailure(e); } @@ -161,9 +178,10 @@ private void handlePrivsResponse(String username, PutDatafeedAction.Request request, HasPrivilegesResponse response, ClusterState clusterState, + Map headers, ActionListener listener) throws IOException { if (response.isCompleteMatch()) { - putDatafeed(request, threadPool.getThreadContext().getHeaders(), clusterState, listener); + putDatafeed(request, headers, clusterState, listener); } else { XContentBuilder builder = JsonXContent.contentBuilder(); builder.startObject(); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java index 41dd9a76c1dbd..864d9bcb9e1be 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java @@ -35,6 +35,8 @@ import java.io.IOException; import java.util.Map; +import static org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth; + public class TransportUpdateDatafeedAction extends TransportMasterNodeAction { @@ -74,7 +76,6 @@ protected void masterOperation(Task task, UpdateDatafeedAction.Request request, return; } - final Map headers = threadPool.getThreadContext().getHeaders(); // Check datafeed is stopped PersistentTasksCustomMetaData tasks = state.getMetaData().custom(PersistentTasksCustomMetaData.TYPE); @@ -85,6 +86,8 @@ protected void masterOperation(Task task, UpdateDatafeedAction.Request request, return; } + final Map headers = extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); + datafeedConfigProvider.updateDatefeedConfig( request.getUpdate().getId(), request.getUpdate(), diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java new file mode 100644 index 0000000000000..cea6d99e56a0b --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.ml.utils; + +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.xpack.core.ClientHelper; +import org.elasticsearch.xpack.core.security.authc.AuthenticationField; +import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public final class AuthHeadersExtractor { + + private AuthHeadersExtractor() {} + + public static Map extractAuthHeadersAndPreferSecondaryAuth(ThreadContext context) { + Map threadHeaders = context.getHeaders(); + if (threadHeaders == null || threadHeaders.isEmpty()) { + return new HashMap<>(); + } + + Map headers = threadHeaders.entrySet().stream() + .filter(e -> ClientHelper.SECURITY_HEADER_FILTERS.contains(e.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + String secondaryAuth = headers.get(SecondaryAuthentication.THREAD_CTX_KEY); + if (secondaryAuth != null) { + headers.put(AuthenticationField.AUTHENTICATION_KEY, secondaryAuth); + } + return headers; + } + +} From d06ae0579cddffd697d5f08f38768a243186bec6 Mon Sep 17 00:00:00 2001 From: Benjamin Trent <4357155+benwtrent@users.noreply.github.com> Date: Mon, 30 Mar 2020 15:29:03 -0400 Subject: [PATCH 2/6] removing secondary auth header when storing and preferring it as primary --- .../org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java index cea6d99e56a0b..cef34407333d3 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java @@ -28,7 +28,7 @@ public static Map extractAuthHeadersAndPreferSecondaryAuth(Threa Map headers = threadHeaders.entrySet().stream() .filter(e -> ClientHelper.SECURITY_HEADER_FILTERS.contains(e.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - String secondaryAuth = headers.get(SecondaryAuthentication.THREAD_CTX_KEY); + String secondaryAuth = headers.remove(SecondaryAuthentication.THREAD_CTX_KEY); if (secondaryAuth != null) { headers.put(AuthenticationField.AUTHENTICATION_KEY, secondaryAuth); } From 3ed4cff3448bed284fbf6d0c3726044fa9cdf557 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Tue, 31 Mar 2020 14:24:52 -0400 Subject: [PATCH 3/6] Update DatafeedJobsRestIT.java --- .../elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java index 0899e4a8729e9..01e6a77bd73e3 100644 --- a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java +++ b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java @@ -549,7 +549,7 @@ public void testSecondaryAuthSearchPrivilegesLookBack() throws Exception { String jobId = "secondary-privs-put-job"; createJob(jobId, "airline.keyword"); String datafeedId = "datafeed-" + jobId; - // Primary auth header does not have accesss, but secondary auth does + // Primary auth header does not have access, but secondary auth does new DatafeedBuilder(datafeedId, jobId, "airline-data") .setAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN) .setSecondaryAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN_WITH_SOME_DATA_ACCESS) From 4674ffaf3e11e14088f178dc5e426688bb996a05 Mon Sep 17 00:00:00 2001 From: Benjamin Trent <4357155+benwtrent@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:03:47 -0400 Subject: [PATCH 4/6] adjusting how secondary auth headers are added --- .../xpack/core/ClientHelper.java | 3 +- .../support/SecondaryAuthentication.java | 2 +- .../TransportPreviewDatafeedAction.java | 71 +++++++----- .../TransportPutDataFrameAnalyticsAction.java | 62 +++++----- .../ml/action/TransportPutDatafeedAction.java | 108 ++++++++---------- .../action/TransportUpdateDatafeedAction.java | 34 +++--- .../xpack/ml/utils/AuthHeadersExtractor.java | 38 ------ .../ml/utils/SecondaryAuthorizationUtils.java | 31 +++++ 8 files changed, 165 insertions(+), 184 deletions(-) delete mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/SecondaryAuthorizationUtils.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java index bf6aba195baa3..4723da8a3a920 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java @@ -35,8 +35,7 @@ public final class ClientHelper { * List of headers that are related to security */ public static final Set SECURITY_HEADER_FILTERS = Sets.newHashSet(AuthenticationServiceField.RUN_AS_USER_HEADER, - AuthenticationField.AUTHENTICATION_KEY, - SecondaryAuthentication.THREAD_CTX_KEY); + AuthenticationField.AUTHENTICATION_KEY); /** * . diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java index dc940925cd119..213c94e7dad3f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/SecondaryAuthentication.java @@ -25,7 +25,7 @@ */ public class SecondaryAuthentication { - public static final String THREAD_CTX_KEY = "_xpack_security_secondary_authc"; + private static final String THREAD_CTX_KEY = "_xpack_security_secondary_authc"; private final SecurityContext securityContext; private final Authentication authentication; diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java index 255967d592c7e..cfb36edd1861a 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPreviewDatafeedAction.java @@ -11,14 +11,18 @@ import org.elasticsearch.client.Client; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.ClientHelper; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.ml.action.PreviewDatafeedAction; import org.elasticsearch.xpack.core.ml.datafeed.ChunkingConfig; import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig; import org.elasticsearch.xpack.core.ml.datafeed.extractor.DataExtractor; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.ml.datafeed.DatafeedTimingStatsReporter; import org.elasticsearch.xpack.ml.datafeed.extractor.DataExtractorFactory; import org.elasticsearch.xpack.ml.datafeed.persistence.DatafeedConfigProvider; @@ -33,7 +37,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import static org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth; +import static org.elasticsearch.xpack.ml.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable; public class TransportPreviewDatafeedAction extends HandledTransportAction { @@ -43,9 +47,10 @@ public class TransportPreviewDatafeedAction extends HandledTransportAction { DatafeedConfig.Builder previewDatafeed = buildPreviewDatafeed(datafeedConfig); - Map headers = extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); - previewDatafeed.setHeaders(headers); - jobResultsProvider.datafeedTimingStats( - jobBuilder.getId(), - timingStats -> { - // NB: this is using the client from the transport layer, NOT the internal client. - // This is important because it means the datafeed search will fail if the user - // requesting the preview doesn't have permission to search the relevant indices. - DataExtractorFactory.create( - client, - previewDatafeed.build(), - jobBuilder.build(), - xContentRegistry, - // Fake DatafeedTimingStatsReporter that does not have access to results index - new DatafeedTimingStatsReporter(timingStats, (ts, refreshPolicy) -> {}), - new ActionListener<>() { - @Override - public void onResponse(DataExtractorFactory dataExtractorFactory) { - DataExtractor dataExtractor = dataExtractorFactory.newExtractor(0, Long.MAX_VALUE); - threadPool.generic().execute(() -> previewDatafeed(dataExtractor, listener)); - } + useSecondaryAuthIfAvailable(securityContext, () -> { + Map headers = threadPool.getThreadContext().getHeaders().entrySet().stream() + .filter(e -> ClientHelper.SECURITY_HEADER_FILTERS.contains(e.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + previewDatafeed.setHeaders(headers); + jobResultsProvider.datafeedTimingStats( + jobBuilder.getId(), + timingStats -> { + // NB: this is using the client from the transport layer, NOT the internal client. + // This is important because it means the datafeed search will fail if the user + // requesting the preview doesn't have permission to search the relevant indices. + DataExtractorFactory.create( + client, + previewDatafeed.build(), + jobBuilder.build(), + xContentRegistry, + // Fake DatafeedTimingStatsReporter that does not have access to results index + new DatafeedTimingStatsReporter(timingStats, (ts, refreshPolicy) -> {}), + new ActionListener<>() { + @Override + public void onResponse(DataExtractorFactory dataExtractorFactory) { + DataExtractor dataExtractor = dataExtractorFactory.newExtractor(0, Long.MAX_VALUE); + threadPool.generic().execute(() -> previewDatafeed(dataExtractor, listener)); + } - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }); - }, - listener::onFailure); + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } + }); + }, + listener::onFailure); + }); }, listener::onFailure)); }, diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java index a014600bc44bd..1bde7378d88a4 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDataFrameAnalyticsAction.java @@ -57,9 +57,7 @@ import java.util.Map; import java.util.Objects; -import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; -import static org.elasticsearch.xpack.core.ClientHelper.executeWithHeadersAsync; -import static org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth; +import static org.elasticsearch.xpack.ml.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable; public class TransportPutDataFrameAnalyticsAction extends TransportMasterNodeAction { @@ -144,35 +142,29 @@ private void putValidatedConfig(DataFrameAnalyticsConfig config, ActionListener< .build(); if (licenseState.isAuthAllowed()) { - final Map authHeaders = extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); - // If we have secondary auth, prefer it. - final String username = securityContext.getSecondaryAuthentication() == null ? - securityContext.getUser().principal() : - securityContext.getSecondaryAuthentication().getAuthentication().getUser().principal(); - RoleDescriptor.IndicesPrivileges sourceIndexPrivileges = RoleDescriptor.IndicesPrivileges.builder() - .indices(preparedForPutConfig.getSource().getIndex()) - .privileges("read") - .build(); - RoleDescriptor.IndicesPrivileges destIndexPrivileges = RoleDescriptor.IndicesPrivileges.builder() - .indices(preparedForPutConfig.getDest().getIndex()) - .privileges("read", "index", "create_index") - .build(); - - HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); - privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]); - privRequest.username(username); - privRequest.clusterPrivileges(Strings.EMPTY_ARRAY); - privRequest.indexPrivileges(sourceIndexPrivileges, destIndexPrivileges); - - ActionListener privResponseListener = ActionListener.wrap( - r -> handlePrivsResponse(username, preparedForPutConfig, r, authHeaders, listener), - listener::onFailure); - executeWithHeadersAsync(authHeaders, - ML_ORIGIN, - client, - HasPrivilegesAction.INSTANCE, - privRequest, - privResponseListener); + useSecondaryAuthIfAvailable(securityContext, () -> { + final String username = securityContext.getUser().principal(); + RoleDescriptor.IndicesPrivileges sourceIndexPrivileges = RoleDescriptor.IndicesPrivileges.builder() + .indices(preparedForPutConfig.getSource().getIndex()) + .privileges("read") + .build(); + RoleDescriptor.IndicesPrivileges destIndexPrivileges = RoleDescriptor.IndicesPrivileges.builder() + .indices(preparedForPutConfig.getDest().getIndex()) + .privileges("read", "index", "create_index") + .build(); + + HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); + privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]); + privRequest.username(username); + privRequest.clusterPrivileges(Strings.EMPTY_ARRAY); + privRequest.indexPrivileges(sourceIndexPrivileges, destIndexPrivileges); + + ActionListener privResponseListener = ActionListener.wrap( + r -> handlePrivsResponse(username, preparedForPutConfig, r, listener), + listener::onFailure); + + client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener); + }); } else { updateDocMappingAndPutConfig( preparedForPutConfig, @@ -184,15 +176,13 @@ private void putValidatedConfig(DataFrameAnalyticsConfig config, ActionListener< } } - private void handlePrivsResponse(String username, - DataFrameAnalyticsConfig memoryCappedConfig, + private void handlePrivsResponse(String username, DataFrameAnalyticsConfig memoryCappedConfig, HasPrivilegesResponse response, - Map authHeaders, ActionListener listener) throws IOException { if (response.isCompleteMatch()) { updateDocMappingAndPutConfig( memoryCappedConfig, - authHeaders, + threadPool.getThreadContext().getHeaders(), ActionListener.wrap( indexResponse -> listener.onResponse(new PutDataFrameAnalyticsAction.Response(memoryCappedConfig)), listener::onFailure diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java index 770c23d2dad7f..cd0b1fae59d95 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java @@ -49,12 +49,12 @@ import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse; +import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.ResourcePrivileges; import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.ml.datafeed.persistence.DatafeedConfigProvider; import org.elasticsearch.xpack.ml.job.persistence.JobConfigProvider; -import org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor; import java.io.IOException; import java.util.Collections; @@ -62,7 +62,7 @@ import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; -import static org.elasticsearch.xpack.core.ClientHelper.executeWithHeadersAsync; +import static org.elasticsearch.xpack.ml.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable; public class TransportPutDatafeedAction extends TransportMasterNodeAction { @@ -108,67 +108,52 @@ protected void masterOperation(Task task, PutDatafeedAction.Request request, Clu // If security is enabled only create the datafeed if the user requesting creation has // permission to read the indices the datafeed is going to read from if (licenseState.isAuthAllowed()) { - - final String[] indices = request.getDatafeed().getIndices().toArray(new String[0]); - - // If we have secondary auth, prefer it. - final String username = securityContext.getSecondaryAuthentication() == null ? - securityContext.getUser().principal() : - securityContext.getSecondaryAuthentication().getAuthentication().getUser().principal(); - Map authHeaders = AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); - - final HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); - privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]); - privRequest.username(username); - privRequest.clusterPrivileges(Strings.EMPTY_ARRAY); - - final RoleDescriptor.IndicesPrivileges.Builder indicesPrivilegesBuilder = RoleDescriptor.IndicesPrivileges.builder() - .indices(indices); - - ActionListener privResponseListener = ActionListener.wrap( - r -> handlePrivsResponse(username, request, r, state, authHeaders, listener), - listener::onFailure); - - ActionListener getRollupIndexCapsActionHandler = ActionListener.wrap( - response -> { - if (response.getJobs().isEmpty()) { // This means no rollup indexes are in the config - indicesPrivilegesBuilder.privileges(SearchAction.NAME); - } else { - indicesPrivilegesBuilder.privileges(SearchAction.NAME, RollupSearchAction.NAME); - } - privRequest.indexPrivileges(indicesPrivilegesBuilder.build()); - executeWithHeadersAsync(authHeaders, - ML_ORIGIN, - client, - HasPrivilegesAction.INSTANCE, - privRequest, - privResponseListener); - }, - e -> { - if (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) { - indicesPrivilegesBuilder.privileges(SearchAction.NAME); + useSecondaryAuthIfAvailable(securityContext, () -> { + final String[] indices = request.getDatafeed().getIndices().toArray(new String[0]); + + final String username = securityContext.getUser().principal(); + final HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); + privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]); + privRequest.username(username); + privRequest.clusterPrivileges(Strings.EMPTY_ARRAY); + + final RoleDescriptor.IndicesPrivileges.Builder indicesPrivilegesBuilder = RoleDescriptor.IndicesPrivileges.builder() + .indices(indices); + + ActionListener privResponseListener = ActionListener.wrap( + r -> handlePrivsResponse(username, request, r, state, listener), + listener::onFailure); + + ActionListener getRollupIndexCapsActionHandler = ActionListener.wrap( + response -> { + if (response.getJobs().isEmpty()) { // This means no rollup indexes are in the config + indicesPrivilegesBuilder.privileges(SearchAction.NAME); + } else { + indicesPrivilegesBuilder.privileges(SearchAction.NAME, RollupSearchAction.NAME); + } privRequest.indexPrivileges(indicesPrivilegesBuilder.build()); - executeWithHeadersAsync(authHeaders, - ML_ORIGIN, - client, - HasPrivilegesAction.INSTANCE, - privRequest, - privResponseListener); - } else { - listener.onFailure(e); + client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener); + }, + e -> { + if (ExceptionsHelper.unwrapCause(e) instanceof IndexNotFoundException) { + indicesPrivilegesBuilder.privileges(SearchAction.NAME); + privRequest.indexPrivileges(indicesPrivilegesBuilder.build()); + client.execute(HasPrivilegesAction.INSTANCE, privRequest, privResponseListener); + } else { + listener.onFailure(e); + } } + ); + if (RemoteClusterLicenseChecker.containsRemoteIndex(request.getDatafeed().getIndices())) { + getRollupIndexCapsActionHandler.onResponse(new GetRollupIndexCapsAction.Response()); + } else { + executeAsyncWithOrigin(client, + ML_ORIGIN, + GetRollupIndexCapsAction.INSTANCE, + new GetRollupIndexCapsAction.Request(indices), + getRollupIndexCapsActionHandler); } - ); - if (RemoteClusterLicenseChecker.containsRemoteIndex(request.getDatafeed().getIndices())) { - getRollupIndexCapsActionHandler.onResponse(new GetRollupIndexCapsAction.Response()); - } else { - executeAsyncWithOrigin(client, - ML_ORIGIN, - GetRollupIndexCapsAction.INSTANCE, - new GetRollupIndexCapsAction.Request(indices), - getRollupIndexCapsActionHandler); - } - + }); } else { putDatafeed(request, threadPool.getThreadContext().getHeaders(), state, listener); } @@ -178,10 +163,9 @@ private void handlePrivsResponse(String username, PutDatafeedAction.Request request, HasPrivilegesResponse response, ClusterState clusterState, - Map headers, ActionListener listener) throws IOException { if (response.isCompleteMatch()) { - putDatafeed(request, headers, clusterState, listener); + putDatafeed(request, threadPool.getThreadContext().getHeaders(), clusterState, listener); } else { XContentBuilder builder = JsonXContent.contentBuilder(); builder.startObject(); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java index 5723d67df6ec4..1be533ce84a5c 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportUpdateDatafeedAction.java @@ -22,12 +22,14 @@ import org.elasticsearch.tasks.Task; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.PutDatafeedAction; import org.elasticsearch.xpack.core.ml.action.UpdateDatafeedAction; import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState; import org.elasticsearch.xpack.core.ml.job.messages.Messages; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; +import org.elasticsearch.xpack.core.security.SecurityContext; import org.elasticsearch.xpack.ml.MlConfigMigrationEligibilityCheck; import org.elasticsearch.xpack.ml.datafeed.persistence.DatafeedConfigProvider; import org.elasticsearch.xpack.ml.job.persistence.JobConfigProvider; @@ -35,7 +37,7 @@ import java.io.IOException; import java.util.Map; -import static org.elasticsearch.xpack.ml.utils.AuthHeadersExtractor.extractAuthHeadersAndPreferSecondaryAuth; +import static org.elasticsearch.xpack.ml.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable; public class TransportUpdateDatafeedAction extends TransportMasterNodeAction { @@ -43,6 +45,7 @@ public class TransportUpdateDatafeedAction extends private final DatafeedConfigProvider datafeedConfigProvider; private final JobConfigProvider jobConfigProvider; private final MlConfigMigrationEligibilityCheck migrationEligibilityCheck; + private final SecurityContext securityContext; @Inject public TransportUpdateDatafeedAction(Settings settings, TransportService transportService, ClusterService clusterService, @@ -55,6 +58,8 @@ public TransportUpdateDatafeedAction(Settings settings, TransportService transpo this.datafeedConfigProvider = new DatafeedConfigProvider(client, xContentRegistry); this.jobConfigProvider = new JobConfigProvider(client, xContentRegistry); this.migrationEligibilityCheck = new MlConfigMigrationEligibilityCheck(settings, clusterService); + this.securityContext = XPackSettings.SECURITY_ENABLED.get(settings) ? + new SecurityContext(settings, threadPool.getThreadContext()) : null; } @Override @@ -75,27 +80,26 @@ protected void masterOperation(Task task, UpdateDatafeedAction.Request request, listener.onFailure(ExceptionsHelper.configHasNotBeenMigrated("update datafeed", request.getUpdate().getId())); return; } - - // Check datafeed is stopped PersistentTasksCustomMetadata tasks = state.getMetadata().custom(PersistentTasksCustomMetadata.TYPE); if (MlTasks.getDatafeedTask(request.getUpdate().getId(), tasks) != null) { listener.onFailure(ExceptionsHelper.conflictStatusException( - Messages.getMessage(Messages.DATAFEED_CANNOT_UPDATE_IN_CURRENT_STATE, - request.getUpdate().getId(), DatafeedState.STARTED))); + Messages.getMessage(Messages.DATAFEED_CANNOT_UPDATE_IN_CURRENT_STATE, + request.getUpdate().getId(), DatafeedState.STARTED))); return; } - final Map headers = extractAuthHeadersAndPreferSecondaryAuth(threadPool.getThreadContext()); - - datafeedConfigProvider.updateDatefeedConfig( - request.getUpdate().getId(), - request.getUpdate(), - headers, - jobConfigProvider::validateDatafeedJob, - ActionListener.wrap( - updatedConfig -> listener.onResponse(new PutDatafeedAction.Response(updatedConfig)), - listener::onFailure)); + useSecondaryAuthIfAvailable(securityContext, () -> { + final Map headers = threadPool.getThreadContext().getHeaders(); + datafeedConfigProvider.updateDatefeedConfig( + request.getUpdate().getId(), + request.getUpdate(), + headers, + jobConfigProvider::validateDatafeedJob, + ActionListener.wrap( + updatedConfig -> listener.onResponse(new PutDatafeedAction.Response(updatedConfig)), + listener::onFailure)); + }); } @Override diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java deleted file mode 100644 index cef34407333d3..0000000000000 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/AuthHeadersExtractor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -package org.elasticsearch.xpack.ml.utils; - -import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.xpack.core.ClientHelper; -import org.elasticsearch.xpack.core.security.authc.AuthenticationField; -import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; - -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -public final class AuthHeadersExtractor { - - private AuthHeadersExtractor() {} - - public static Map extractAuthHeadersAndPreferSecondaryAuth(ThreadContext context) { - Map threadHeaders = context.getHeaders(); - if (threadHeaders == null || threadHeaders.isEmpty()) { - return new HashMap<>(); - } - - Map headers = threadHeaders.entrySet().stream() - .filter(e -> ClientHelper.SECURITY_HEADER_FILTERS.contains(e.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - String secondaryAuth = headers.remove(SecondaryAuthentication.THREAD_CTX_KEY); - if (secondaryAuth != null) { - headers.put(AuthenticationField.AUTHENTICATION_KEY, secondaryAuth); - } - return headers; - } - -} diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/SecondaryAuthorizationUtils.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/SecondaryAuthorizationUtils.java new file mode 100644 index 0000000000000..701ccc50035d7 --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/utils/SecondaryAuthorizationUtils.java @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.ml.utils; + +import org.elasticsearch.xpack.core.security.SecurityContext; +import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; + +public final class SecondaryAuthorizationUtils { + + private SecondaryAuthorizationUtils() {} + + /** + * This executes the supplied runnable inside the secondary auth context if it exists; + */ + public static void useSecondaryAuthIfAvailable(SecurityContext securityContext, Runnable runnable) { + if (securityContext == null) { + runnable.run(); + return; + } + SecondaryAuthentication secondaryAuth = securityContext.getSecondaryAuthentication(); + if (secondaryAuth != null) { + runnable = secondaryAuth.wrap(runnable); + } + runnable.run(); + } + +} From 4b93bd5bbeac647aeb96485170d0ac79a514aabb Mon Sep 17 00:00:00 2001 From: Benjamin Trent <4357155+benwtrent@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:08:29 -0400 Subject: [PATCH 5/6] fixing minor things --- .../src/main/java/org/elasticsearch/xpack/core/ClientHelper.java | 1 - .../xpack/ml/action/TransportPutDatafeedAction.java | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java index 4723da8a3a920..1b79d0090e5d5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java @@ -17,7 +17,6 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField; -import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; import java.util.Map; import java.util.Set; diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java index cd0b1fae59d95..7f907e67fc039 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutDatafeedAction.java @@ -49,7 +49,6 @@ import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse; -import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.ResourcePrivileges; import org.elasticsearch.xpack.core.security.support.Exceptions; From b7be96d277ae3733d61c822b7ceebac993e9dbfc Mon Sep 17 00:00:00 2001 From: Benjamin Trent <4357155+benwtrent@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:18:41 -0400 Subject: [PATCH 6/6] reverting unnecessary change --- .../main/java/org/elasticsearch/xpack/core/ClientHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java index 1b79d0090e5d5..49a457f0a7f55 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ClientHelper.java @@ -34,7 +34,7 @@ public final class ClientHelper { * List of headers that are related to security */ public static final Set SECURITY_HEADER_FILTERS = Sets.newHashSet(AuthenticationServiceField.RUN_AS_USER_HEADER, - AuthenticationField.AUTHENTICATION_KEY); + AuthenticationField.AUTHENTICATION_KEY); /** * .