diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformDeprecations.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformDeprecations.java index 79a679441de3a..1de584d5593f1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformDeprecations.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformDeprecations.java @@ -27,5 +27,12 @@ public class TransformDeprecations { public static final String MAX_PAGE_SEARCH_SIZE_BREAKING_CHANGES_URL = "https://ela.st/es-deprecation-7-transform-max-page-search-size"; + public static final String DATA_FRAME_TRANSFORMS_ROLES_BREAKING_CHANGES_URL = + "https://ela.st/es-deprecation-9-data-frame-transforms-roles"; + + public static final String DATA_FRAME_TRANSFORMS_ROLES_IS_DEPRECATED = "This transform configuration uses one or more obsolete roles " + + "prefixed with [data_frame_transformers_] which will be unsupported after the next upgrade. Switch to a user with the equivalent " + + "roles prefixed with [transform_] and use [/_transform/_upgrade] to upgrade all transforms to the latest roles.";; + private TransformDeprecations() {} } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfig.java index d8972dcf6a6be..745da71539992 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfig.java @@ -24,11 +24,13 @@ import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.common.time.TimeUtils; import org.elasticsearch.xpack.core.common.validation.SourceDestValidator; import org.elasticsearch.xpack.core.common.validation.SourceDestValidator.SourceDestValidation; import org.elasticsearch.xpack.core.deprecation.DeprecationIssue; import org.elasticsearch.xpack.core.deprecation.DeprecationIssue.Level; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.xcontent.XContentUtils; import org.elasticsearch.xpack.core.transform.TransformConfigVersion; import org.elasticsearch.xpack.core.transform.TransformDeprecations; @@ -41,6 +43,7 @@ import java.io.IOException; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -49,6 +52,7 @@ import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; +import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.AUTHENTICATION_KEY; /** * This class holds the configuration details of a data frame transform @@ -65,6 +69,10 @@ public final class TransformConfig implements SimpleDiffable, W public static final ParseField HEADERS = new ParseField("headers"); /** Version in which {@code FieldCapabilitiesRequest.runtime_fields} field was introduced. */ private static final TransportVersion FIELD_CAPS_RUNTIME_MAPPINGS_INTRODUCED_TRANSPORT_VERSION = TransportVersions.V_7_12_0; + private static final List DEPRECATED_DATA_FRAME_TRANSFORMS_ROLES = List.of( + "data_frame_transforms_admin", + "data_frame_transforms_user" + ); /** Specifies all the possible transform functions. */ public enum Function { @@ -374,7 +382,7 @@ public ActionRequestValidationException validate(ActionRequestValidationExceptio * @param namedXContentRegistry XContent registry required for aggregations and query DSL * @return The deprecations of this transform */ - public List checkForDeprecations(NamedXContentRegistry namedXContentRegistry) { + public List checkForDeprecations(NamedXContentRegistry namedXContentRegistry) throws IOException { List deprecations = new ArrayList<>(); @@ -404,9 +412,38 @@ public List checkForDeprecations(NamedXContentRegistry namedXC if (retentionPolicyConfig != null) { retentionPolicyConfig.checkForDeprecations(getId(), namedXContentRegistry, deprecations::add); } + + var deprecatedTransformRoles = getRolesFromHeaders().stream().filter(DEPRECATED_DATA_FRAME_TRANSFORMS_ROLES::contains).toList(); + if (deprecatedTransformRoles.isEmpty() == false) { + deprecations.add( + new DeprecationIssue( + Level.CRITICAL, + "Transform [" + id + "] uses deprecated transform roles " + deprecatedTransformRoles, + TransformDeprecations.DATA_FRAME_TRANSFORMS_ROLES_BREAKING_CHANGES_URL, + TransformDeprecations.DATA_FRAME_TRANSFORMS_ROLES_IS_DEPRECATED, + false, + null + ) + ); + } + return deprecations; } + private List getRolesFromHeaders() throws IOException { + if (headers == null) { + return Collections.emptyList(); + } + + var encodedAuthenticationHeader = ClientHelper.filterSecurityHeaders(headers).getOrDefault(AUTHENTICATION_KEY, ""); + if (encodedAuthenticationHeader.isEmpty()) { + return Collections.emptyList(); + } + + var decodedAuthenticationHeader = AuthenticationContextSerializer.decode(encodedAuthenticationHeader); + return Arrays.asList(decodedAuthenticationHeader.getEffectiveSubject().getUser().roles()); + } + @Override public void writeTo(final StreamOutput out) throws IOException { out.writeString(id); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfigTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfigTests.java index 8cfecc432c661..2e7e5293c835f 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfigTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/transform/transforms/TransformConfigTests.java @@ -27,6 +27,8 @@ import org.elasticsearch.xpack.core.common.validation.SourceDestValidator.SourceDestValidation; import org.elasticsearch.xpack.core.deprecation.DeprecationIssue; import org.elasticsearch.xpack.core.deprecation.DeprecationIssue.Level; +import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper; +import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.transform.AbstractSerializingTransformTestCase; import org.elasticsearch.xpack.core.transform.TransformConfigVersion; import org.elasticsearch.xpack.core.transform.TransformDeprecations; @@ -44,6 +46,7 @@ import java.util.Map; import static org.elasticsearch.test.TestMatchers.matchesPattern; +import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.AUTHENTICATION_KEY; import static org.elasticsearch.xpack.core.transform.transforms.DestConfigTests.randomDestConfig; import static org.elasticsearch.xpack.core.transform.transforms.SourceConfigTests.randomInvalidSourceConfig; import static org.elasticsearch.xpack.core.transform.transforms.SourceConfigTests.randomSourceConfig; @@ -58,6 +61,8 @@ public class TransformConfigTests extends AbstractSerializingTransformTestCase headers) { + return new TransformConfig( + randomAlphaOfLengthBetween(1, 10), + randomSourceConfig(), + randomDestConfig(), + randomBoolean() ? null : TimeValue.timeValueMillis(randomIntBetween(1_000, 3_600_000)), + randomBoolean() ? null : randomSyncConfig(), + headers, + randomBoolean() ? null : PivotConfigTests.randomPivotConfig(), + randomBoolean() ? null : LatestConfigTests.randomLatestConfig(), + randomBoolean() ? null : randomAlphaOfLengthBetween(1, 1000), + randomBoolean() ? null : SettingsConfigTests.randomSettingsConfig(), + randomBoolean() ? null : randomMetadata(), + randomBoolean() ? null : randomRetentionPolicyConfig(), + randomBoolean() ? null : Instant.now(), + TransformConfigVersion.CURRENT.toString() + ); + } + public static TransformConfig randomTransformConfig( String id, TransformConfigVersion version, @@ -915,10 +939,13 @@ public void testGroupByStayInOrder() throws IOException { } } - public void testCheckForDeprecations() { + public void testCheckForDeprecations_NoDeprecationWarnings() throws IOException { String id = randomAlphaOfLengthBetween(1, 10); assertThat(randomTransformConfig(id, TransformConfigVersion.CURRENT).checkForDeprecations(xContentRegistry()), is(empty())); + } + public void testCheckForDeprecations_WithDeprecatedFields_VersionCurrent() throws IOException { + String id = randomAlphaOfLengthBetween(1, 10); TransformConfig deprecatedConfig = randomTransformConfigWithDeprecatedFields(id, TransformConfigVersion.CURRENT); // check _and_ clear warnings @@ -940,8 +967,11 @@ public void testCheckForDeprecations() { ) ) ); + } - deprecatedConfig = randomTransformConfigWithDeprecatedFields(id, TransformConfigVersion.V_7_10_0); + public void testCheckForDeprecations_WithDeprecatedFields_Version_7_10() throws IOException { + String id = randomAlphaOfLengthBetween(1, 10); + TransformConfig deprecatedConfig = randomTransformConfigWithDeprecatedFields(id, TransformConfigVersion.V_7_10_0); // check _and_ clear warnings assertWarnings(TransformDeprecations.ACTION_MAX_PAGE_SEARCH_SIZE_IS_DEPRECATED); @@ -962,8 +992,11 @@ public void testCheckForDeprecations() { ) ) ); + } - deprecatedConfig = randomTransformConfigWithDeprecatedFields(id, TransformConfigVersion.V_7_4_0); + public void testCheckForDeprecations_WithDeprecatedFields_Version_7_4() throws IOException { + String id = randomAlphaOfLengthBetween(1, 10); + TransformConfig deprecatedConfig = randomTransformConfigWithDeprecatedFields(id, TransformConfigVersion.V_7_4_0); // check _and_ clear warnings assertWarnings(TransformDeprecations.ACTION_MAX_PAGE_SEARCH_SIZE_IS_DEPRECATED); @@ -994,6 +1027,44 @@ public void testCheckForDeprecations() { ); } + public void testCheckForDeprecations_WithDeprecatedTransformUserAdmin() throws IOException { + testCheckForDeprecations_WithDeprecatedRoles(List.of(DATA_FRAME_TRANSFORMS_ADMIN_ROLE)); + } + + public void testCheckForDeprecations_WithDeprecatedTransformUserRole() throws IOException { + testCheckForDeprecations_WithDeprecatedRoles(List.of(DATA_FRAME_TRANSFORMS_USER_ROLE)); + } + + public void testCheckForDeprecations_WithDeprecatedTransformRoles() throws IOException { + testCheckForDeprecations_WithDeprecatedRoles(List.of(DATA_FRAME_TRANSFORMS_ADMIN_ROLE, DATA_FRAME_TRANSFORMS_USER_ROLE)); + } + + private void testCheckForDeprecations_WithDeprecatedRoles(List roles) throws IOException { + var authentication = AuthenticationTestHelper.builder() + .realm() + .user(new User(randomAlphaOfLength(10), roles.toArray(String[]::new))) + .build(); + Map headers = Map.of(AUTHENTICATION_KEY, authentication.encode()); + TransformConfig deprecatedConfig = randomTransformConfigWithHeaders(headers); + + // important: checkForDeprecations does _not_ create new deprecation warnings + assertThat( + deprecatedConfig.checkForDeprecations(xContentRegistry()), + equalTo( + List.of( + new DeprecationIssue( + Level.CRITICAL, + "Transform [" + deprecatedConfig.getId() + "] uses deprecated transform roles " + roles, + TransformDeprecations.DATA_FRAME_TRANSFORMS_ROLES_BREAKING_CHANGES_URL, + TransformDeprecations.DATA_FRAME_TRANSFORMS_ROLES_IS_DEPRECATED, + false, + null + ) + ) + ) + ); + } + public void testSerializingMetadataPreservesOrder() throws IOException { String json = Strings.format(""" {