mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-25 07:37:19 -04:00
[ML] [Transforms] prefer secondary auth headers for transforms (#86757)
When creating and updating transforms, it is possible for clients to provide secondary headers. When PUT, _preview, _update is called with secondary authorization headers, those are then used or stored with the transform. closes: https://github.com/elastic/elasticsearch/issues/86731
This commit is contained in:
parent
132633e998
commit
b90b3450a2
13 changed files with 379 additions and 194 deletions
5
docs/changelog/86757.yaml
Normal file
5
docs/changelog/86757.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pr: 86757
|
||||||
|
summary: "Prefer secondary auth headers for transforms"
|
||||||
|
area: Transform
|
||||||
|
type: enhancement
|
||||||
|
issues: []
|
|
@ -30,7 +30,12 @@ Requires the following privileges:
|
||||||
* cluster: `manage_transform` (the `transform_admin` built-in role grants this
|
* cluster: `manage_transform` (the `transform_admin` built-in role grants this
|
||||||
privilege)
|
privilege)
|
||||||
* source indices: `read`, `view_index_metadata`.
|
* source indices: `read`, `view_index_metadata`.
|
||||||
|
+
|
||||||
|
--
|
||||||
|
NOTE: If you provide
|
||||||
|
<<http-clients-secondary-authorization,secondary authorization headers>>, those
|
||||||
|
credentials are used.
|
||||||
|
--
|
||||||
[[preview-transform-desc]]
|
[[preview-transform-desc]]
|
||||||
== {api-description-title}
|
== {api-description-title}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,12 @@ Requires the following privileges:
|
||||||
* source indices: `read`, `view_index_metadata`
|
* source indices: `read`, `view_index_metadata`
|
||||||
* destination index: `read`, `create_index`, `index`. If a `retention_policy` is configured, the `delete` privilege is
|
* destination index: `read`, `create_index`, `index`. If a `retention_policy` is configured, the `delete` privilege is
|
||||||
also required.
|
also required.
|
||||||
|
+
|
||||||
|
--
|
||||||
|
NOTE: If you provide
|
||||||
|
<<http-clients-secondary-authorization,secondary authorization headers>>, those
|
||||||
|
credentials are used.
|
||||||
|
--
|
||||||
|
|
||||||
[[put-transform-desc]]
|
[[put-transform-desc]]
|
||||||
== {api-description-title}
|
== {api-description-title}
|
||||||
|
|
|
@ -44,7 +44,9 @@ each checkpoint.
|
||||||
|
|
||||||
* When {es} {security-features} are enabled, your {transform} remembers which
|
* When {es} {security-features} are enabled, your {transform} remembers which
|
||||||
roles the user who updated it had at the time of update and runs with those
|
roles the user who updated it had at the time of update and runs with those
|
||||||
privileges.
|
privileges. If you provide
|
||||||
|
<<http-clients-secondary-authorization,secondary authorization headers>>, those
|
||||||
|
credentials are used instead.
|
||||||
* You must use {kib} or this API to update a {transform}. Do not update a
|
* You must use {kib} or this API to update a {transform}. Do not update a
|
||||||
{transform} directly via `.transform-internal*` indices using the {es} index API.
|
{transform} directly via `.transform-internal*` indices using the {es} index API.
|
||||||
If {es} {security-features} are enabled, do not give users any privileges on
|
If {es} {security-features} are enabled, do not give users any privileges on
|
||||||
|
|
|
@ -42,12 +42,14 @@ import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
public class TransformPivotRestIT extends TransformRestTestCase {
|
public class TransformPivotRestIT extends TransformRestTestCase {
|
||||||
|
|
||||||
|
private static final String TEST_USER_NAME_NO_ACCESS = "no_authorization";
|
||||||
private static final String TEST_USER_NAME = "transform_admin_plus_data";
|
private static final String TEST_USER_NAME = "transform_admin_plus_data";
|
||||||
private static final String DATA_ACCESS_ROLE = "test_data_access";
|
private static final String DATA_ACCESS_ROLE = "test_data_access";
|
||||||
private static final String BASIC_AUTH_VALUE_TRANSFORM_ADMIN_WITH_SOME_DATA_ACCESS = basicAuthHeaderValue(
|
private static final String BASIC_AUTH_VALUE_TRANSFORM_ADMIN_WITH_SOME_DATA_ACCESS = basicAuthHeaderValue(
|
||||||
TEST_USER_NAME,
|
TEST_USER_NAME,
|
||||||
TEST_PASSWORD_SECURE_STRING
|
TEST_PASSWORD_SECURE_STRING
|
||||||
);
|
);
|
||||||
|
private static final String BASIC_AUTH_VALUE_NO_ACCESS = basicAuthHeaderValue(TEST_USER_NAME_NO_ACCESS, TEST_PASSWORD_SECURE_STRING);
|
||||||
|
|
||||||
private static boolean indicesCreated = false;
|
private static boolean indicesCreated = false;
|
||||||
|
|
||||||
|
@ -96,6 +98,34 @@ public class TransformPivotRestIT extends TransformRestTestCase {
|
||||||
assertOneCount(transformIndex + "/_search?q=reviewer:user_26", "hits.hits._source.affiliate_missing", 0);
|
assertOneCount(transformIndex + "/_search?q=reviewer:user_26", "hits.hits._source.affiliate_missing", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSimplePivotWithSecondaryHeaders() throws Exception {
|
||||||
|
setupUser(TEST_USER_NAME_NO_ACCESS, List.of("transform_admin"));
|
||||||
|
String transformId = "simple-pivot";
|
||||||
|
String transformIndex = "pivot_reviews";
|
||||||
|
setupDataAccessRole(DATA_ACCESS_ROLE, REVIEWS_INDEX_NAME, transformIndex);
|
||||||
|
createPivotReviewsTransform(
|
||||||
|
transformId,
|
||||||
|
transformIndex,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
BASIC_AUTH_VALUE_NO_ACCESS,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_WITH_SOME_DATA_ACCESS,
|
||||||
|
REVIEWS_INDEX_NAME
|
||||||
|
);
|
||||||
|
startAndWaitForTransform(
|
||||||
|
transformId,
|
||||||
|
transformIndex,
|
||||||
|
BASIC_AUTH_VALUE_NO_ACCESS,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_WITH_SOME_DATA_ACCESS,
|
||||||
|
new String[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
// we expect 27 documents as there shall be 27 user_id's
|
||||||
|
// Just need to validate that things ran with secondary headers
|
||||||
|
Map<String, Object> indexStats = getAsMap(transformIndex + "/_stats");
|
||||||
|
assertEquals(27, XContentMapValues.extractValue("_all.total.docs.count", indexStats));
|
||||||
|
}
|
||||||
|
|
||||||
public void testSimpleDataStreamPivot() throws Exception {
|
public void testSimpleDataStreamPivot() throws Exception {
|
||||||
String indexName = "reviews_data_stream";
|
String indexName = "reviews_data_stream";
|
||||||
createReviewsIndex(indexName, 1000, 27, "date", true, -1, null);
|
createReviewsIndex(indexName, 1000, 27, "date", true, -1, null);
|
||||||
|
|
|
@ -43,6 +43,7 @@ import static org.hamcrest.Matchers.is;
|
||||||
public abstract class TransformRestTestCase extends ESRestTestCase {
|
public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
protected static final String TEST_PASSWORD = "x-pack-test-password";
|
protected static final String TEST_PASSWORD = "x-pack-test-password";
|
||||||
|
private static final String SECONDARY_AUTH_KEY = "es-secondary-authorization";
|
||||||
protected static final SecureString TEST_PASSWORD_SECURE_STRING = new SecureString(TEST_PASSWORD.toCharArray());
|
protected static final SecureString TEST_PASSWORD_SECURE_STRING = new SecureString(TEST_PASSWORD.toCharArray());
|
||||||
private static final String BASIC_AUTH_VALUE_SUPER_USER = basicAuthHeaderValue("x_pack_rest_user", TEST_PASSWORD_SECURE_STRING);
|
private static final String BASIC_AUTH_VALUE_SUPER_USER = basicAuthHeaderValue("x_pack_rest_user", TEST_PASSWORD_SECURE_STRING);
|
||||||
|
|
||||||
|
@ -267,7 +268,7 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
}""".formatted(transformIndex, REVIEWS_INDEX_NAME);
|
}""".formatted(transformIndex, REVIEWS_INDEX_NAME);
|
||||||
|
|
||||||
createReviewsTransform(transformId, authHeader, config);
|
createReviewsTransform(transformId, authHeader, null, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createPivotReviewsTransform(
|
protected void createPivotReviewsTransform(
|
||||||
|
@ -277,6 +278,18 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
String pipeline,
|
String pipeline,
|
||||||
String authHeader,
|
String authHeader,
|
||||||
String sourceIndex
|
String sourceIndex
|
||||||
|
) throws IOException {
|
||||||
|
createPivotReviewsTransform(transformId, transformIndex, query, pipeline, authHeader, null, sourceIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createPivotReviewsTransform(
|
||||||
|
String transformId,
|
||||||
|
String transformIndex,
|
||||||
|
String query,
|
||||||
|
String pipeline,
|
||||||
|
String authHeader,
|
||||||
|
String secondaryAuthHeader,
|
||||||
|
String sourceIndex
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
String config = "{";
|
String config = "{";
|
||||||
|
|
||||||
|
@ -326,7 +339,7 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
"frequency": "1s"
|
"frequency": "1s"
|
||||||
}""";
|
}""";
|
||||||
|
|
||||||
createReviewsTransform(transformId, authHeader, config);
|
createReviewsTransform(transformId, authHeader, secondaryAuthHeader, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void createLatestReviewsTransform(String transformId, String transformIndex) throws IOException {
|
protected void createLatestReviewsTransform(String transformId, String transformIndex) throws IOException {
|
||||||
|
@ -347,11 +360,17 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
"frequency": "1s"
|
"frequency": "1s"
|
||||||
}""".formatted(transformIndex, REVIEWS_INDEX_NAME);
|
}""".formatted(transformIndex, REVIEWS_INDEX_NAME);
|
||||||
|
|
||||||
createReviewsTransform(transformId, null, config);
|
createReviewsTransform(transformId, null, null, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createReviewsTransform(String transformId, String authHeader, String config) throws IOException {
|
private void createReviewsTransform(String transformId, String authHeader, String secondaryAuthHeader, String config)
|
||||||
final Request createTransformRequest = createRequestWithAuth("PUT", getTransformEndpoint() + transformId, authHeader);
|
throws IOException {
|
||||||
|
final Request createTransformRequest = createRequestWithSecondaryAuth(
|
||||||
|
"PUT",
|
||||||
|
getTransformEndpoint() + transformId,
|
||||||
|
authHeader,
|
||||||
|
secondaryAuthHeader
|
||||||
|
);
|
||||||
createTransformRequest.setJsonEntity(config);
|
createTransformRequest.setJsonEntity(config);
|
||||||
|
|
||||||
Map<String, Object> createTransformResponse = entityAsMap(client().performRequest(createTransformRequest));
|
Map<String, Object> createTransformResponse = entityAsMap(client().performRequest(createTransformRequest));
|
||||||
|
@ -360,7 +379,7 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
protected void createPivotReviewsTransform(String transformId, String transformIndex, String query, String pipeline, String authHeader)
|
protected void createPivotReviewsTransform(String transformId, String transformIndex, String query, String pipeline, String authHeader)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
createPivotReviewsTransform(transformId, transformIndex, query, pipeline, authHeader, REVIEWS_INDEX_NAME);
|
createPivotReviewsTransform(transformId, transformIndex, query, pipeline, authHeader, null, REVIEWS_INDEX_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startTransform(String transformId) throws IOException {
|
protected void startTransform(String transformId) throws IOException {
|
||||||
|
@ -369,7 +388,18 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
protected void startTransform(String transformId, String authHeader, String... warnings) throws IOException {
|
protected void startTransform(String transformId, String authHeader, String... warnings) throws IOException {
|
||||||
// start the transform
|
// start the transform
|
||||||
final Request startTransformRequest = createRequestWithAuth("POST", getTransformEndpoint() + transformId + "/_start", authHeader);
|
startTransform(transformId, authHeader, null, warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startTransform(String transformId, String authHeader, String secondaryAuthHeader, String... warnings)
|
||||||
|
throws IOException {
|
||||||
|
// start the transform
|
||||||
|
final Request startTransformRequest = createRequestWithSecondaryAuth(
|
||||||
|
"POST",
|
||||||
|
getTransformEndpoint() + transformId + "/_start",
|
||||||
|
authHeader,
|
||||||
|
secondaryAuthHeader
|
||||||
|
);
|
||||||
if (warnings.length > 0) {
|
if (warnings.length > 0) {
|
||||||
startTransformRequest.setOptions(expectWarnings(warnings));
|
startTransformRequest.setOptions(expectWarnings(warnings));
|
||||||
}
|
}
|
||||||
|
@ -404,8 +434,18 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
protected void startAndWaitForTransform(String transformId, String transformIndex, String authHeader, String... warnings)
|
protected void startAndWaitForTransform(String transformId, String transformIndex, String authHeader, String... warnings)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
startAndWaitForTransform(transformId, transformIndex, authHeader, null, warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startAndWaitForTransform(
|
||||||
|
String transformId,
|
||||||
|
String transformIndex,
|
||||||
|
String authHeader,
|
||||||
|
String secondaryAuthHeader,
|
||||||
|
String... warnings
|
||||||
|
) throws Exception {
|
||||||
// start the transform
|
// start the transform
|
||||||
startTransform(transformId, authHeader, warnings);
|
startTransform(transformId, authHeader, secondaryAuthHeader, warnings);
|
||||||
assertTrue(indexExists(transformIndex));
|
assertTrue(indexExists(transformIndex));
|
||||||
// wait until the transform has been created and all data is available
|
// wait until the transform has been created and all data is available
|
||||||
waitForTransformCheckpoint(transformId);
|
waitForTransformCheckpoint(transformId);
|
||||||
|
@ -435,16 +475,27 @@ public abstract class TransformRestTestCase extends ESRestTestCase {
|
||||||
assertThat(resetTransformResponse.get("acknowledged"), equalTo(Boolean.TRUE));
|
assertThat(resetTransformResponse.get("acknowledged"), equalTo(Boolean.TRUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Request createRequestWithAuth(final String method, final String endpoint, final String authHeader) {
|
protected Request createRequestWithSecondaryAuth(
|
||||||
|
final String method,
|
||||||
|
final String endpoint,
|
||||||
|
final String authHeader,
|
||||||
|
final String secondaryAuthHeader
|
||||||
|
) {
|
||||||
final Request request = new Request(method, endpoint);
|
final Request request = new Request(method, endpoint);
|
||||||
|
|
||||||
if (authHeader != null) {
|
|
||||||
RequestOptions.Builder options = request.getOptions().toBuilder();
|
RequestOptions.Builder options = request.getOptions().toBuilder();
|
||||||
|
if (authHeader != null) {
|
||||||
options.addHeader("Authorization", authHeader);
|
options.addHeader("Authorization", authHeader);
|
||||||
|
}
|
||||||
|
if (secondaryAuthHeader != null) {
|
||||||
|
options.addHeader(SECONDARY_AUTH_KEY, secondaryAuthHeader);
|
||||||
|
}
|
||||||
request.setOptions(options);
|
request.setOptions(options);
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
return request;
|
protected Request createRequestWithAuth(final String method, final String endpoint, final String authHeader) {
|
||||||
|
return createRequestWithSecondaryAuth(method, endpoint, authHeader, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void waitForTransformStopped(String transformId) throws Exception {
|
void waitForTransformStopped(String transformId) throws Exception {
|
||||||
|
|
|
@ -38,15 +38,18 @@ public class TransformUpdateIT extends TransformRestTestCase {
|
||||||
TEST_ADMIN_USER_NAME_2,
|
TEST_ADMIN_USER_NAME_2,
|
||||||
TEST_PASSWORD_SECURE_STRING
|
TEST_PASSWORD_SECURE_STRING
|
||||||
);
|
);
|
||||||
|
private static final String TEST_ADMIN_USER_NAME_NO_DATA = "transform_admin_no_data";
|
||||||
|
private static final String BASIC_AUTH_VALUE_TRANSFORM_ADMIN_NO_DATA = basicAuthHeaderValue(
|
||||||
|
TEST_ADMIN_USER_NAME_NO_DATA,
|
||||||
|
TEST_PASSWORD_SECURE_STRING
|
||||||
|
);
|
||||||
private static final String DATA_ACCESS_ROLE = "test_data_access";
|
private static final String DATA_ACCESS_ROLE = "test_data_access";
|
||||||
private static final String DATA_ACCESS_ROLE_2 = "test_data_access_2";
|
private static final String DATA_ACCESS_ROLE_2 = "test_data_access_2";
|
||||||
|
|
||||||
private static boolean indicesCreated = false;
|
|
||||||
|
|
||||||
// preserve indices in order to reuse source indices in several test cases
|
// preserve indices in order to reuse source indices in several test cases
|
||||||
@Override
|
@Override
|
||||||
protected boolean preserveIndicesUponCompletion() {
|
protected boolean preserveIndicesUponCompletion() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,14 +73,8 @@ public class TransformUpdateIT extends TransformRestTestCase {
|
||||||
setupUser(TEST_USER_NAME, Arrays.asList("transform_user", DATA_ACCESS_ROLE));
|
setupUser(TEST_USER_NAME, Arrays.asList("transform_user", DATA_ACCESS_ROLE));
|
||||||
setupUser(TEST_ADMIN_USER_NAME_1, Arrays.asList("transform_admin", DATA_ACCESS_ROLE));
|
setupUser(TEST_ADMIN_USER_NAME_1, Arrays.asList("transform_admin", DATA_ACCESS_ROLE));
|
||||||
setupUser(TEST_ADMIN_USER_NAME_2, Arrays.asList("transform_admin", DATA_ACCESS_ROLE_2));
|
setupUser(TEST_ADMIN_USER_NAME_2, Arrays.asList("transform_admin", DATA_ACCESS_ROLE_2));
|
||||||
|
setupUser(TEST_ADMIN_USER_NAME_NO_DATA, List.of("transform_admin"));
|
||||||
// it's not possible to run it as @BeforeClass as clients aren't initialized then, so we need this little hack
|
|
||||||
if (indicesCreated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
createReviewsIndex();
|
createReviewsIndex();
|
||||||
indicesCreated = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -149,8 +146,15 @@ public class TransformUpdateIT extends TransformRestTestCase {
|
||||||
assertThat(XContentMapValues.extractValue("settings.max_page_search_size", transform), equalTo(555));
|
assertThat(XContentMapValues.extractValue("settings.max_page_search_size", transform), equalTo(555));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void testUpdateTransferRights() throws Exception {
|
public void testUpdateTransferRights() throws Exception {
|
||||||
|
updateTransferRightsTester(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUpdateTransferRightsSecondaryAuthHeaders() throws Exception {
|
||||||
|
updateTransferRightsTester(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTransferRightsTester(boolean useSecondaryAuthHeaders) throws Exception {
|
||||||
String transformId = "transform1";
|
String transformId = "transform1";
|
||||||
// Note: Due to a bug the transform does not fail to start after deleting the user and role, therefore invalidating
|
// Note: Due to a bug the transform does not fail to start after deleting the user and role, therefore invalidating
|
||||||
// the credentials stored with the config. As a workaround we use a 2nd transform that uses the same config
|
// the credentials stored with the config. As a workaround we use a 2nd transform that uses the same config
|
||||||
|
@ -160,17 +164,23 @@ public class TransformUpdateIT extends TransformRestTestCase {
|
||||||
setupDataAccessRole(DATA_ACCESS_ROLE, REVIEWS_INDEX_NAME, transformDest);
|
setupDataAccessRole(DATA_ACCESS_ROLE, REVIEWS_INDEX_NAME, transformDest);
|
||||||
setupDataAccessRole(DATA_ACCESS_ROLE_2, REVIEWS_INDEX_NAME, transformDest);
|
setupDataAccessRole(DATA_ACCESS_ROLE_2, REVIEWS_INDEX_NAME, transformDest);
|
||||||
|
|
||||||
final Request createTransformRequest = createRequestWithAuth(
|
final Request createTransformRequest = useSecondaryAuthHeaders
|
||||||
|
? createRequestWithSecondaryAuth(
|
||||||
"PUT",
|
"PUT",
|
||||||
getTransformEndpoint() + transformId,
|
getTransformEndpoint() + transformId,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_NO_DATA,
|
||||||
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_2
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_2
|
||||||
);
|
)
|
||||||
|
: createRequestWithAuth("PUT", getTransformEndpoint() + transformId, BASIC_AUTH_VALUE_TRANSFORM_ADMIN_2);
|
||||||
|
|
||||||
final Request createTransformRequest_2 = createRequestWithAuth(
|
final Request createTransformRequest_2 = useSecondaryAuthHeaders
|
||||||
|
? createRequestWithSecondaryAuth(
|
||||||
"PUT",
|
"PUT",
|
||||||
getTransformEndpoint() + transformIdCloned,
|
getTransformEndpoint() + transformIdCloned,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_NO_DATA,
|
||||||
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_2
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_2
|
||||||
);
|
)
|
||||||
|
: createRequestWithAuth("PUT", getTransformEndpoint() + transformIdCloned, BASIC_AUTH_VALUE_TRANSFORM_ADMIN_2);
|
||||||
|
|
||||||
String config = """
|
String config = """
|
||||||
{
|
{
|
||||||
|
@ -229,24 +239,37 @@ public class TransformUpdateIT extends TransformRestTestCase {
|
||||||
assertEquals(1, XContentMapValues.extractValue("count", transforms));
|
assertEquals(1, XContentMapValues.extractValue("count", transforms));
|
||||||
|
|
||||||
// start using admin 1, but as the header is still admin 2
|
// start using admin 1, but as the header is still admin 2
|
||||||
|
// This fails as the stored header is still admin 2
|
||||||
try {
|
try {
|
||||||
|
if (useSecondaryAuthHeaders) {
|
||||||
|
startAndWaitForTransform(
|
||||||
|
transformId,
|
||||||
|
transformDest,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_NO_DATA,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1,
|
||||||
|
new String[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
startAndWaitForTransform(transformId, transformDest, BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1);
|
startAndWaitForTransform(transformId, transformDest, BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1);
|
||||||
|
}
|
||||||
fail("request should have failed");
|
fail("request should have failed");
|
||||||
} catch (ResponseException e) {
|
} catch (ResponseException e) {
|
||||||
assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(500));
|
assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(500));
|
||||||
}
|
}
|
||||||
|
|
||||||
assertBusy(() -> {
|
assertBusy(() -> {
|
||||||
Map<?, ?> transformStatsAsMap = getTransformStateAndStats(transformId);
|
Map<?, ?> transformStatsAsMap = getTransformStateAndStats(transformId);
|
||||||
assertThat(XContentMapValues.extractValue("stats.documents_indexed", transformStatsAsMap), equalTo(0));
|
assertThat(XContentMapValues.extractValue("stats.documents_indexed", transformStatsAsMap), equalTo(0));
|
||||||
}, 3, TimeUnit.SECONDS);
|
}, 3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
// update the transform with an empty body, the credentials (headers) should change
|
// update the transform with an empty body, the credentials (headers) should change
|
||||||
final Request updateRequest = createRequestWithAuth(
|
final Request updateRequest = useSecondaryAuthHeaders
|
||||||
|
? createRequestWithSecondaryAuth(
|
||||||
"POST",
|
"POST",
|
||||||
getTransformEndpoint() + transformIdCloned + "/_update",
|
getTransformEndpoint() + transformIdCloned + "/_update",
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_NO_DATA,
|
||||||
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1
|
||||||
);
|
)
|
||||||
|
: createRequestWithAuth("POST", getTransformEndpoint() + transformIdCloned + "/_update", BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1);
|
||||||
updateRequest.setJsonEntity("{}");
|
updateRequest.setJsonEntity("{}");
|
||||||
assertOK(client().performRequest(updateRequest));
|
assertOK(client().performRequest(updateRequest));
|
||||||
|
|
||||||
|
@ -256,8 +279,17 @@ public class TransformUpdateIT extends TransformRestTestCase {
|
||||||
assertEquals(1, XContentMapValues.extractValue("count", transforms));
|
assertEquals(1, XContentMapValues.extractValue("count", transforms));
|
||||||
|
|
||||||
// start with updated configuration should succeed
|
// start with updated configuration should succeed
|
||||||
|
if (useSecondaryAuthHeaders) {
|
||||||
|
startAndWaitForTransform(
|
||||||
|
transformIdCloned,
|
||||||
|
transformDest,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_NO_DATA,
|
||||||
|
BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1,
|
||||||
|
new String[0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
startAndWaitForTransform(transformIdCloned, transformDest, BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1);
|
startAndWaitForTransform(transformIdCloned, transformDest, BASIC_AUTH_VALUE_TRANSFORM_ADMIN_1);
|
||||||
|
}
|
||||||
assertBusy(() -> {
|
assertBusy(() -> {
|
||||||
Map<?, ?> transformStatsAsMap = getTransformStateAndStats(transformIdCloned);
|
Map<?, ?> transformStatsAsMap = getTransformStateAndStats(transformIdCloned);
|
||||||
assertThat(XContentMapValues.extractValue("stats.documents_indexed", transformStatsAsMap), equalTo(27));
|
assertThat(XContentMapValues.extractValue("stats.documents_indexed", transformStatsAsMap), equalTo(27));
|
||||||
|
|
|
@ -24,9 +24,11 @@ import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
import static org.elasticsearch.xpack.transform.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link TransformPrivilegeChecker} is responsible for checking whether the user has the right privileges in order to work with transform.
|
* {@link TransformPrivilegeChecker} is responsible for checking whether the user has the right privileges in order to work with transform.
|
||||||
|
@ -43,6 +45,7 @@ final class TransformPrivilegeChecker {
|
||||||
boolean checkDestIndexPrivileges,
|
boolean checkDestIndexPrivileges,
|
||||||
ActionListener<Void> listener
|
ActionListener<Void> listener
|
||||||
) {
|
) {
|
||||||
|
useSecondaryAuthIfAvailable(securityContext, () -> {
|
||||||
final String username = securityContext.getUser().principal();
|
final String username = securityContext.getUser().principal();
|
||||||
|
|
||||||
ActionListener<HasPrivilegesResponse> hasPrivilegesResponseListener = ActionListener.wrap(
|
ActionListener<HasPrivilegesResponse> hasPrivilegesResponseListener = ActionListener.wrap(
|
||||||
|
@ -58,6 +61,7 @@ final class TransformPrivilegeChecker {
|
||||||
checkDestIndexPrivileges
|
checkDestIndexPrivileges
|
||||||
);
|
);
|
||||||
client.execute(HasPrivilegesAction.INSTANCE, hasPrivilegesRequest, hasPrivilegesResponseListener);
|
client.execute(HasPrivilegesAction.INSTANCE, hasPrivilegesRequest, hasPrivilegesResponseListener);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HasPrivilegesRequest buildPrivilegesRequest(
|
private static HasPrivilegesRequest buildPrivilegesRequest(
|
||||||
|
@ -128,7 +132,7 @@ final class TransformPrivilegeChecker {
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(e -> Boolean.TRUE.equals(e.getValue()) == false)
|
.filter(e -> Boolean.TRUE.equals(e.getValue()) == false)
|
||||||
.map(e -> e.getKey())
|
.map(Map.Entry::getKey)
|
||||||
.collect(joining(", ", indexPrivileges.getResource() + ":[", "]"))
|
.collect(joining(", ", indexPrivileges.getResource() + ":[", "]"))
|
||||||
)
|
)
|
||||||
.collect(toList());
|
.collect(toList());
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||||
import org.elasticsearch.ingest.IngestService;
|
import org.elasticsearch.ingest.IngestService;
|
||||||
import org.elasticsearch.license.License;
|
import org.elasticsearch.license.License;
|
||||||
import org.elasticsearch.license.RemoteClusterLicenseChecker;
|
import org.elasticsearch.license.RemoteClusterLicenseChecker;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
@ -64,11 +63,11 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
|
import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder;
|
||||||
import static org.elasticsearch.xpack.core.transform.action.PreviewTransformAction.DUMMY_DEST_INDEX_FOR_PREVIEW;
|
import static org.elasticsearch.xpack.core.transform.action.PreviewTransformAction.DUMMY_DEST_INDEX_FOR_PREVIEW;
|
||||||
|
import static org.elasticsearch.xpack.transform.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable;
|
||||||
|
|
||||||
public class TransportPreviewTransformAction extends HandledTransportAction<Request, Response> {
|
public class TransportPreviewTransformAction extends HandledTransportAction<Request, Response> {
|
||||||
|
|
||||||
private static final int NUMBER_OF_PREVIEW_BUCKETS = 100;
|
private static final int NUMBER_OF_PREVIEW_BUCKETS = 100;
|
||||||
private final XPackLicenseState licenseState;
|
|
||||||
private final SecurityContext securityContext;
|
private final SecurityContext securityContext;
|
||||||
private final IndexNameExpressionResolver indexNameExpressionResolver;
|
private final IndexNameExpressionResolver indexNameExpressionResolver;
|
||||||
private final Client client;
|
private final Client client;
|
||||||
|
@ -80,7 +79,6 @@ public class TransportPreviewTransformAction extends HandledTransportAction<Requ
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TransportPreviewTransformAction(
|
public TransportPreviewTransformAction(
|
||||||
XPackLicenseState licenseState,
|
|
||||||
TransportService transportService,
|
TransportService transportService,
|
||||||
ActionFilters actionFilters,
|
ActionFilters actionFilters,
|
||||||
Client client,
|
Client client,
|
||||||
|
@ -91,7 +89,6 @@ public class TransportPreviewTransformAction extends HandledTransportAction<Requ
|
||||||
IngestService ingestService
|
IngestService ingestService
|
||||||
) {
|
) {
|
||||||
super(PreviewTransformAction.NAME, transportService, actionFilters, Request::new);
|
super(PreviewTransformAction.NAME, transportService, actionFilters, Request::new);
|
||||||
this.licenseState = licenseState;
|
|
||||||
this.securityContext = XPackSettings.SECURITY_ENABLED.get(settings)
|
this.securityContext = XPackSettings.SECURITY_ENABLED.get(settings)
|
||||||
? new SecurityContext(settings, threadPool.getThreadContext())
|
? new SecurityContext(settings, threadPool.getThreadContext())
|
||||||
: null;
|
: null;
|
||||||
|
@ -140,8 +137,10 @@ public class TransportPreviewTransformAction extends HandledTransportAction<Requ
|
||||||
final Function function = FunctionFactory.create(config);
|
final Function function = FunctionFactory.create(config);
|
||||||
|
|
||||||
// <4> Validate transform query
|
// <4> Validate transform query
|
||||||
ActionListener<Boolean> validateConfigListener = ActionListener.wrap(validateConfigResponse -> {
|
ActionListener<Boolean> validateConfigListener = ActionListener.wrap(
|
||||||
getPreview(
|
validateConfigResponse -> useSecondaryAuthIfAvailable(
|
||||||
|
securityContext,
|
||||||
|
() -> getPreview(
|
||||||
config.getId(), // note: @link{PreviewTransformAction} sets an id, so this is never null
|
config.getId(), // note: @link{PreviewTransformAction} sets an id, so this is never null
|
||||||
function,
|
function,
|
||||||
config.getSource(),
|
config.getSource(),
|
||||||
|
@ -149,26 +148,29 @@ public class TransportPreviewTransformAction extends HandledTransportAction<Requ
|
||||||
config.getDestination().getIndex(),
|
config.getDestination().getIndex(),
|
||||||
config.getSyncConfig(),
|
config.getSyncConfig(),
|
||||||
listener
|
listener
|
||||||
|
)
|
||||||
|
),
|
||||||
|
listener::onFailure
|
||||||
);
|
);
|
||||||
}, listener::onFailure);
|
|
||||||
|
|
||||||
// <3> Validate transform function config
|
// <3> Validate transform function config
|
||||||
ActionListener<Boolean> validateSourceDestListener = ActionListener.wrap(
|
ActionListener<Boolean> validateSourceDestListener = ActionListener.wrap(
|
||||||
validateSourceDestResponse -> { function.validateConfig(validateConfigListener); },
|
validateSourceDestResponse -> function.validateConfig(validateConfigListener),
|
||||||
listener::onFailure
|
listener::onFailure
|
||||||
);
|
);
|
||||||
|
|
||||||
// <2> Validate source and destination indices
|
// <2> Validate source and destination indices
|
||||||
ActionListener<Void> checkPrivilegesListener = ActionListener.wrap(aVoid -> {
|
ActionListener<Void> checkPrivilegesListener = ActionListener.wrap(
|
||||||
sourceDestValidator.validate(
|
aVoid -> sourceDestValidator.validate(
|
||||||
clusterState,
|
clusterState,
|
||||||
config.getSource().getIndex(),
|
config.getSource().getIndex(),
|
||||||
config.getDestination().getIndex(),
|
config.getDestination().getIndex(),
|
||||||
config.getDestination().getPipeline(),
|
config.getDestination().getPipeline(),
|
||||||
SourceDestValidations.getValidationsForPreview(config.getAdditionalSourceDestValidations()),
|
SourceDestValidations.getValidationsForPreview(config.getAdditionalSourceDestValidations()),
|
||||||
validateSourceDestListener
|
validateSourceDestListener
|
||||||
|
),
|
||||||
|
listener::onFailure
|
||||||
);
|
);
|
||||||
}, listener::onFailure);
|
|
||||||
|
|
||||||
// <1> Early check to verify that the user can create the destination index and can read from the source
|
// <1> Early check to verify that the user can create the destination index and can read from the source
|
||||||
if (XPackSettings.SECURITY_ENABLED.get(nodeSettings)) {
|
if (XPackSettings.SECURITY_ENABLED.get(nodeSettings)) {
|
||||||
|
@ -228,7 +230,7 @@ public class TransportPreviewTransformAction extends HandledTransportAction<Requ
|
||||||
);
|
);
|
||||||
|
|
||||||
List<String> warnings = TransformConfigLinter.getWarnings(function, source, syncConfig);
|
List<String> warnings = TransformConfigLinter.getWarnings(function, source, syncConfig);
|
||||||
warnings.forEach(warning -> HeaderWarning.addWarning(warning));
|
warnings.forEach(HeaderWarning::addWarning);
|
||||||
listener.onResponse(new Response(docs, generatedDestIndexSettings));
|
listener.onResponse(new Response(docs, generatedDestIndexSettings));
|
||||||
}, listener::onFailure);
|
}, listener::onFailure);
|
||||||
|
|
||||||
|
@ -240,7 +242,7 @@ public class TransportPreviewTransformAction extends HandledTransportAction<Requ
|
||||||
Clock.systemUTC()
|
Clock.systemUTC()
|
||||||
);
|
);
|
||||||
List<String> warnings = TransformConfigLinter.getWarnings(function, source, syncConfig);
|
List<String> warnings = TransformConfigLinter.getWarnings(function, source, syncConfig);
|
||||||
warnings.forEach(warning -> HeaderWarning.addWarning(warning));
|
warnings.forEach(HeaderWarning::addWarning);
|
||||||
listener.onResponse(new Response(docs, generatedDestIndexSettings));
|
listener.onResponse(new Response(docs, generatedDestIndexSettings));
|
||||||
} else {
|
} else {
|
||||||
List<Map<String, Object>> results = docs.stream().map(doc -> {
|
List<Map<String, Object>> results = docs.stream().map(doc -> {
|
||||||
|
|
|
@ -24,7 +24,6 @@ import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.ingest.IngestService;
|
|
||||||
import org.elasticsearch.persistent.PersistentTasksCustomMetadata;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetadata;
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
@ -48,6 +47,8 @@ import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.transform.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable;
|
||||||
|
|
||||||
public class TransportPutTransformAction extends AcknowledgedTransportMasterNodeAction<Request> {
|
public class TransportPutTransformAction extends AcknowledgedTransportMasterNodeAction<Request> {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(TransportPutTransformAction.class);
|
private static final Logger logger = LogManager.getLogger(TransportPutTransformAction.class);
|
||||||
|
@ -67,8 +68,7 @@ public class TransportPutTransformAction extends AcknowledgedTransportMasterNode
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver,
|
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||||
ClusterService clusterService,
|
ClusterService clusterService,
|
||||||
TransformServices transformServices,
|
TransformServices transformServices,
|
||||||
Client client,
|
Client client
|
||||||
IngestService ingestService
|
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
PutTransformAction.NAME,
|
PutTransformAction.NAME,
|
||||||
|
@ -92,38 +92,44 @@ public class TransportPutTransformAction extends AcknowledgedTransportMasterNode
|
||||||
@Override
|
@Override
|
||||||
protected void masterOperation(Task task, Request request, ClusterState clusterState, ActionListener<AcknowledgedResponse> listener) {
|
protected void masterOperation(Task task, Request request, ClusterState clusterState, ActionListener<AcknowledgedResponse> listener) {
|
||||||
XPackPlugin.checkReadyForXPackCustomMetadata(clusterState);
|
XPackPlugin.checkReadyForXPackCustomMetadata(clusterState);
|
||||||
|
useSecondaryAuthIfAvailable(securityContext, () -> {
|
||||||
// set headers to run transform as calling user
|
// set headers to run transform as calling user
|
||||||
Map<String, String> filteredHeaders = ClientHelper.getPersistableSafeSecurityHeaders(
|
Map<String, String> filteredHeaders = ClientHelper.getPersistableSafeSecurityHeaders(
|
||||||
threadPool.getThreadContext(),
|
threadPool.getThreadContext(),
|
||||||
clusterService.state()
|
clusterService.state()
|
||||||
);
|
);
|
||||||
|
|
||||||
TransformConfig config = request.getConfig().setHeaders(filteredHeaders).setCreateTime(Instant.now()).setVersion(Version.CURRENT);
|
TransformConfig config = request.getConfig()
|
||||||
|
.setHeaders(filteredHeaders)
|
||||||
|
.setCreateTime(Instant.now())
|
||||||
|
.setVersion(Version.CURRENT);
|
||||||
|
|
||||||
String transformId = config.getId();
|
String transformId = config.getId();
|
||||||
// quick check whether a transform has already been created under that name
|
// quick check whether a transform has already been created under that name
|
||||||
if (PersistentTasksCustomMetadata.getTaskWithId(clusterState, transformId) != null) {
|
if (PersistentTasksCustomMetadata.getTaskWithId(clusterState, transformId) != null) {
|
||||||
listener.onFailure(
|
listener.onFailure(
|
||||||
new ResourceAlreadyExistsException(TransformMessages.getMessage(TransformMessages.REST_PUT_TRANSFORM_EXISTS, transformId))
|
new ResourceAlreadyExistsException(
|
||||||
|
TransformMessages.getMessage(TransformMessages.REST_PUT_TRANSFORM_EXISTS, transformId)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <3> Create the transform
|
// <3> Create the transform
|
||||||
ActionListener<ValidateTransformAction.Response> validateTransformListener = ActionListener.wrap(
|
ActionListener<ValidateTransformAction.Response> validateTransformListener = ActionListener.wrap(
|
||||||
validationResponse -> { putTransform(request, listener); },
|
validationResponse -> putTransform(request, listener),
|
||||||
listener::onFailure
|
listener::onFailure
|
||||||
);
|
);
|
||||||
|
|
||||||
// <2> Validate source and destination indices
|
// <2> Validate source and destination indices
|
||||||
ActionListener<Void> checkPrivilegesListener = ActionListener.wrap(aVoid -> {
|
ActionListener<Void> checkPrivilegesListener = ActionListener.wrap(
|
||||||
client.execute(
|
aVoid -> client.execute(
|
||||||
ValidateTransformAction.INSTANCE,
|
ValidateTransformAction.INSTANCE,
|
||||||
new ValidateTransformAction.Request(config, request.isDeferValidation(), request.timeout()),
|
new ValidateTransformAction.Request(config, request.isDeferValidation(), request.timeout()),
|
||||||
validateTransformListener
|
validateTransformListener
|
||||||
|
),
|
||||||
|
listener::onFailure
|
||||||
);
|
);
|
||||||
}, listener::onFailure);
|
|
||||||
|
|
||||||
// <1> Early check to verify that the user can create the destination index and can read from the source
|
// <1> Early check to verify that the user can create the destination index and can read from the source
|
||||||
if (XPackSettings.SECURITY_ENABLED.get(settings) && request.isDeferValidation() == false) {
|
if (XPackSettings.SECURITY_ENABLED.get(settings) && request.isDeferValidation() == false) {
|
||||||
|
@ -140,6 +146,7 @@ public class TransportPutTransformAction extends AcknowledgedTransportMasterNode
|
||||||
} else { // No security enabled, just move on
|
} else { // No security enabled, just move on
|
||||||
checkPrivilegesListener.onResponse(null);
|
checkPrivilegesListener.onResponse(null);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.discovery.MasterNotDiscoveredException;
|
import org.elasticsearch.discovery.MasterNotDiscoveredException;
|
||||||
import org.elasticsearch.ingest.IngestService;
|
|
||||||
import org.elasticsearch.persistent.PersistentTasksCustomMetadata;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetadata;
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
@ -51,6 +50,8 @@ import org.elasticsearch.xpack.transform.transforms.TransformTask;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.transform.utils.SecondaryAuthorizationUtils.useSecondaryAuthIfAvailable;
|
||||||
|
|
||||||
public class TransportUpdateTransformAction extends TransportTasksAction<TransformTask, Request, Response, Response> {
|
public class TransportUpdateTransformAction extends TransportTasksAction<TransformTask, Request, Response, Response> {
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(TransportUpdateTransformAction.class);
|
private static final Logger logger = LogManager.getLogger(TransportUpdateTransformAction.class);
|
||||||
|
@ -71,8 +72,7 @@ public class TransportUpdateTransformAction extends TransportTasksAction<Transfo
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver,
|
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||||
ClusterService clusterService,
|
ClusterService clusterService,
|
||||||
TransformServices transformServices,
|
TransformServices transformServices,
|
||||||
Client client,
|
Client client
|
||||||
IngestService ingestService
|
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
UpdateTransformAction.NAME,
|
UpdateTransformAction.NAME,
|
||||||
|
@ -117,7 +117,7 @@ public class TransportUpdateTransformAction extends TransportTasksAction<Transfo
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
useSecondaryAuthIfAvailable(securityContext, () -> {
|
||||||
// set headers to run transform as calling user
|
// set headers to run transform as calling user
|
||||||
Map<String, String> filteredHeaders = ClientHelper.getPersistableSafeSecurityHeaders(
|
Map<String, String> filteredHeaders = ClientHelper.getPersistableSafeSecurityHeaders(
|
||||||
threadPool.getThreadContext(),
|
threadPool.getThreadContext(),
|
||||||
|
@ -129,8 +129,10 @@ public class TransportUpdateTransformAction extends TransportTasksAction<Transfo
|
||||||
|
|
||||||
// GET transform and attempt to update
|
// GET transform and attempt to update
|
||||||
// We don't want the update to complete if the config changed between GET and INDEX
|
// We don't want the update to complete if the config changed between GET and INDEX
|
||||||
transformConfigManager.getTransformConfigurationForUpdate(request.getId(), ActionListener.wrap(configAndVersion -> {
|
transformConfigManager.getTransformConfigurationForUpdate(
|
||||||
TransformUpdater.updateTransform(
|
request.getId(),
|
||||||
|
ActionListener.wrap(
|
||||||
|
configAndVersion -> TransformUpdater.updateTransform(
|
||||||
securityContext,
|
securityContext,
|
||||||
indexNameExpressionResolver,
|
indexNameExpressionResolver,
|
||||||
clusterState,
|
clusterState,
|
||||||
|
@ -175,8 +177,11 @@ public class TransportUpdateTransformAction extends TransportTasksAction<Transfo
|
||||||
}
|
}
|
||||||
listener.onResponse(new Response(updatedConfig));
|
listener.onResponse(new Response(updatedConfig));
|
||||||
}, listener::onFailure)
|
}, listener::onFailure)
|
||||||
|
),
|
||||||
|
listener::onFailure
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}, listener::onFailure));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkTransformConfigAndLogWarnings(TransformConfig config) {
|
private void checkTransformConfigAndLogWarnings(TransformConfig config) {
|
||||||
|
|
|
@ -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
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.transform.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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,8 @@ import org.elasticsearch.core.TimeValue;
|
||||||
import org.elasticsearch.indices.TestIndexNameExpressionResolver;
|
import org.elasticsearch.indices.TestIndexNameExpressionResolver;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.test.client.NoOpClient;
|
import org.elasticsearch.test.client.NoOpClient;
|
||||||
|
import org.elasticsearch.threadpool.TestThreadPool;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.xpack.core.security.SecurityContext;
|
import org.elasticsearch.xpack.core.security.SecurityContext;
|
||||||
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
|
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
|
||||||
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
|
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
|
||||||
|
@ -75,12 +77,8 @@ public class TransformPrivilegeCheckerTests extends ESTestCase {
|
||||||
.setSource(new SourceConfig(SOURCE_INDEX_NAME))
|
.setSource(new SourceConfig(SOURCE_INDEX_NAME))
|
||||||
.setDest(new DestConfig(DEST_INDEX_NAME, null))
|
.setDest(new DestConfig(DEST_INDEX_NAME, null))
|
||||||
.build();
|
.build();
|
||||||
|
private ThreadPool threadPool;
|
||||||
private final SecurityContext securityContext = new SecurityContext(Settings.EMPTY, null) {
|
private SecurityContext securityContext;
|
||||||
public User getUser() {
|
|
||||||
return new User(USER_NAME);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final IndexNameExpressionResolver indexNameExpressionResolver = TestIndexNameExpressionResolver.newInstance();
|
private final IndexNameExpressionResolver indexNameExpressionResolver = TestIndexNameExpressionResolver.newInstance();
|
||||||
private MyMockClient client;
|
private MyMockClient client;
|
||||||
|
|
||||||
|
@ -90,11 +88,18 @@ public class TransformPrivilegeCheckerTests extends ESTestCase {
|
||||||
client.close();
|
client.close();
|
||||||
}
|
}
|
||||||
client = new MyMockClient(getTestName());
|
client = new MyMockClient(getTestName());
|
||||||
|
threadPool = new TestThreadPool("transform_privilege_checker_tests");
|
||||||
|
securityContext = new SecurityContext(Settings.EMPTY, threadPool.getThreadContext()) {
|
||||||
|
public User getUser() {
|
||||||
|
return new User(USER_NAME);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDownClient() {
|
public void tearDownClient() {
|
||||||
client.close();
|
client.close();
|
||||||
|
threadPool.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCheckPrivileges_NoCheckDestIndexPrivileges() {
|
public void testCheckPrivileges_NoCheckDestIndexPrivileges() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue