diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IngestionPipelineOwnerInheritanceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IngestionPipelineOwnerInheritanceIT.java
new file mode 100644
index 000000000000..964090dc9cfa
--- /dev/null
+++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/IngestionPipelineOwnerInheritanceIT.java
@@ -0,0 +1,244 @@
+package org.openmetadata.it.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.openmetadata.it.factories.DashboardServiceTestFactory;
+import org.openmetadata.it.util.SdkClients;
+import org.openmetadata.it.util.TestNamespace;
+import org.openmetadata.it.util.TestNamespaceExtension;
+import org.openmetadata.schema.api.policies.CreatePolicy;
+import org.openmetadata.schema.api.services.ingestionPipelines.CreateIngestionPipeline;
+import org.openmetadata.schema.api.teams.CreateRole;
+import org.openmetadata.schema.api.teams.CreateUser;
+import org.openmetadata.schema.entity.policies.Policy;
+import org.openmetadata.schema.entity.policies.accessControl.Rule;
+import org.openmetadata.schema.entity.services.DashboardService;
+import org.openmetadata.schema.entity.services.ingestionPipelines.AirflowConfig;
+import org.openmetadata.schema.entity.services.ingestionPipelines.IngestionPipeline;
+import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineType;
+import org.openmetadata.schema.entity.teams.Role;
+import org.openmetadata.schema.entity.teams.User;
+import org.openmetadata.schema.metadataIngestion.DashboardServiceMetadataPipeline;
+import org.openmetadata.schema.metadataIngestion.SourceConfig;
+import org.openmetadata.schema.type.EntityReference;
+import org.openmetadata.schema.type.MetadataOperation;
+import org.openmetadata.sdk.client.OpenMetadataClient;
+import org.openmetadata.sdk.network.HttpMethod;
+
+/**
+ * Integration tests for IngestionPipeline owner inheritance and trigger authorization.
+ *
+ *
entities) {
if (entities == null || entities.isEmpty()) {
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1129/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1129/Migration.java
new file mode 100644
index 000000000000..4d3924ae4610
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1129/Migration.java
@@ -0,0 +1,22 @@
+package org.openmetadata.service.migration.mysql.v1129;
+
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerOperationToDefaultBotPolicies;
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerRuleToDataStewardPolicy;
+
+import lombok.SneakyThrows;
+import org.openmetadata.service.migration.api.MigrationProcessImpl;
+import org.openmetadata.service.migration.utils.MigrationFile;
+
+public class Migration extends MigrationProcessImpl {
+
+ public Migration(MigrationFile migrationFile) {
+ super(migrationFile);
+ }
+
+ @Override
+ @SneakyThrows
+ public void runDataMigration() {
+ addTriggerOperationToDefaultBotPolicies(collectionDAO);
+ addTriggerRuleToDataStewardPolicy(collectionDAO);
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java
index 9584febe0a43..d7592a4c154a 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/mysql/v1130/Migration.java
@@ -1,5 +1,8 @@
package org.openmetadata.service.migration.mysql.v1130;
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerOperationToDefaultBotPolicies;
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerRuleToDataStewardPolicy;
+
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.service.migration.api.MigrationProcessImpl;
@@ -31,5 +34,7 @@ public void runDataMigration() {
LOG.error("v1130 glossaryTerm version relatedTerms transform failed; re-run to retry.", e);
}
MigrationUtil.addTableColumnSearchSettings();
+ addTriggerOperationToDefaultBotPolicies(collectionDAO);
+ addTriggerRuleToDataStewardPolicy(collectionDAO);
}
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1129/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1129/Migration.java
new file mode 100644
index 000000000000..9b008f413532
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1129/Migration.java
@@ -0,0 +1,22 @@
+package org.openmetadata.service.migration.postgres.v1129;
+
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerOperationToDefaultBotPolicies;
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerRuleToDataStewardPolicy;
+
+import lombok.SneakyThrows;
+import org.openmetadata.service.migration.api.MigrationProcessImpl;
+import org.openmetadata.service.migration.utils.MigrationFile;
+
+public class Migration extends MigrationProcessImpl {
+
+ public Migration(MigrationFile migrationFile) {
+ super(migrationFile);
+ }
+
+ @Override
+ @SneakyThrows
+ public void runDataMigration() {
+ addTriggerOperationToDefaultBotPolicies(collectionDAO);
+ addTriggerRuleToDataStewardPolicy(collectionDAO);
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java
index 64f070b32790..d43dee9f5c11 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/postgres/v1130/Migration.java
@@ -1,5 +1,8 @@
package org.openmetadata.service.migration.postgres.v1130;
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerOperationToDefaultBotPolicies;
+import static org.openmetadata.service.migration.utils.v1129.MigrationUtil.addTriggerRuleToDataStewardPolicy;
+
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.service.migration.api.MigrationProcessImpl;
@@ -31,5 +34,7 @@ public void runDataMigration() {
LOG.error("v1130 glossaryTerm version relatedTerms transform failed; re-run to retry.", e);
}
MigrationUtil.addTableColumnSearchSettings();
+ addTriggerOperationToDefaultBotPolicies(collectionDAO);
+ addTriggerRuleToDataStewardPolicy(collectionDAO);
}
}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1129/MigrationUtil.java b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1129/MigrationUtil.java
new file mode 100644
index 000000000000..77ce7f54fe46
--- /dev/null
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/migration/utils/v1129/MigrationUtil.java
@@ -0,0 +1,88 @@
+package org.openmetadata.service.migration.utils.v1129;
+
+import static org.openmetadata.service.migration.utils.v160.MigrationUtil.addOperationsToPolicyRule;
+
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.openmetadata.schema.entity.policies.Policy;
+import org.openmetadata.schema.entity.policies.accessControl.Rule;
+import org.openmetadata.schema.type.Include;
+import org.openmetadata.schema.type.MetadataOperation;
+import org.openmetadata.schema.utils.JsonUtils;
+import org.openmetadata.service.Entity;
+import org.openmetadata.service.exception.EntityNotFoundException;
+import org.openmetadata.service.jdbi3.CollectionDAO;
+import org.openmetadata.service.jdbi3.PolicyRepository;
+
+@Slf4j
+public class MigrationUtil {
+
+ private MigrationUtil() {}
+
+ /**
+ * Retrofits seeded bot policies that grant broad {@code EditAll} on {@code ["All"]} resources
+ * with the {@code Trigger} operation. Pre-fix these identities could trigger pipelines because
+ * {@code /trigger} skipped authz; the migration preserves that behavior under the new authz
+ * enforcement (GH-27962).
+ *
+ * Each entry is idempotent via {@link
+ * org.openmetadata.service.migration.utils.v160.MigrationUtil#addOperationsToPolicyRule}.
+ */
+ public static void addTriggerOperationToDefaultBotPolicies(CollectionDAO collectionDAO) {
+ record PolicyRule(String policy, String rule) {}
+ List targets =
+ List.of(
+ new PolicyRule("IngestionBotPolicy", "IngestionBotRule-Allow"),
+ new PolicyRule("LineageBotPolicy", "LineageBotRule-Allow"),
+ new PolicyRule("ProfilerBotPolicy", "ProfilerBotBotRule-Allow"),
+ new PolicyRule("QualityBotPolicy", "QualityBotBotRule-Allow"),
+ new PolicyRule("UsageBotPolicy", "UsageBotRule-Allow-Usage"));
+ for (PolicyRule t : targets) {
+ addOperationsToPolicyRule(
+ t.policy(), t.rule(), List.of(MetadataOperation.TRIGGER), collectionDAO);
+ }
+ }
+
+ /**
+ * Adds a dedicated {@code DataStewardPolicy-TriggerRule} to the existing {@code
+ * DataStewardPolicy} if not already present. Data stewards already have {@code EditOwners} on
+ * all resources, so they could already reach trigger via an ownership rewrite; this rule makes
+ * the capability explicit for audit clarity rather than burying it inside the existing edit
+ * rule.
+ *
+ * Mirrors the new-rule shape used by {@code
+ * v180.MigrationUtil.addDenyDisplayNameRuleToBotPolicies}. Idempotent — skips when the rule
+ * already exists.
+ */
+ public static void addTriggerRuleToDataStewardPolicy(CollectionDAO collectionDAO) {
+ PolicyRepository repository = (PolicyRepository) Entity.getEntityRepository(Entity.POLICY);
+ try {
+ Policy policy = repository.findByName("DataStewardPolicy", Include.NON_DELETED);
+ boolean hasTriggerRule =
+ policy.getRules().stream()
+ .anyMatch(
+ r ->
+ "DataStewardPolicy-TriggerRule".equals(r.getName())
+ && r.getEffect() == Rule.Effect.ALLOW
+ && r.getOperations() != null
+ && r.getOperations().contains(MetadataOperation.TRIGGER));
+ if (!hasTriggerRule) {
+ Rule triggerRule =
+ new Rule()
+ .withName("DataStewardPolicy-TriggerRule")
+ .withResources(List.of("all"))
+ .withOperations(List.of(MetadataOperation.TRIGGER))
+ .withEffect(Rule.Effect.ALLOW);
+ policy.getRules().add(triggerRule);
+ collectionDAO
+ .policyDAO()
+ .update(policy.getId(), policy.getFullyQualifiedName(), JsonUtils.pojoToJson(policy));
+ LOG.info("Added DataStewardPolicy-TriggerRule to DataStewardPolicy");
+ } else {
+ LOG.debug("DataStewardPolicy already has TriggerRule, skipping");
+ }
+ } catch (EntityNotFoundException ex) {
+ LOG.warn("DataStewardPolicy not found, skipping TriggerRule addition");
+ }
+ }
+}
diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java
index 9216cdd4eb2b..510ff88282af 100644
--- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java
+++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ingestionpipelines/IngestionPipelineResource.java
@@ -1337,6 +1337,8 @@ private PipelineServiceClientResponse deployPipelineInternal(
public PipelineServiceClientResponse triggerPipelineInternal(
UUID id, UriInfo uriInfo, SecurityContext securityContext, String botName) {
+ OperationContext operationContext = new OperationContext(entityType, MetadataOperation.TRIGGER);
+ authorizer.authorize(securityContext, operationContext, getResourceContextById(id));
if (pipelineServiceClient == null) {
return new PipelineServiceClientResponse()
.withCode(200)
@@ -1346,7 +1348,6 @@ public PipelineServiceClientResponse triggerPipelineInternal(
IngestionPipeline ingestionPipeline = repository.get(uriInfo, id, fields);
CreateResourceContext createResourceContext =
new CreateResourceContext<>(entityType, ingestionPipeline);
- OperationContext operationContext = new OperationContext(entityType, MetadataOperation.TRIGGER);
limits.enforceLimits(securityContext, createResourceContext, operationContext);
if (CommonUtil.nullOrEmpty(botName)) {
// Use Default Ingestion Bot
diff --git a/openmetadata-service/src/main/resources/json/data/policy/DataStewardPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/DataStewardPolicy.json
index 76a586bfcb12..6b894ace543b 100644
--- a/openmetadata-service/src/main/resources/json/data/policy/DataStewardPolicy.json
+++ b/openmetadata-service/src/main/resources/json/data/policy/DataStewardPolicy.json
@@ -12,6 +12,12 @@
"resources" : ["all"],
"operations": ["ViewAll", "EditDescription", "EditDisplayName", "EditLineage", "EditOwners", "EditTags", "EditTier", "EditGlossaryTerms", "EditCertification"],
"effect": "allow"
+ },
+ {
+ "name": "DataStewardPolicy-TriggerRule",
+ "resources": ["all"],
+ "operations": ["Trigger"],
+ "effect": "allow"
}
]
-}
\ No newline at end of file
+}
diff --git a/openmetadata-service/src/main/resources/json/data/policy/IngestionBotPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/IngestionBotPolicy.json
index 7b75e412f8fd..dc7cb21edaf3 100644
--- a/openmetadata-service/src/main/resources/json/data/policy/IngestionBotPolicy.json
+++ b/openmetadata-service/src/main/resources/json/data/policy/IngestionBotPolicy.json
@@ -11,7 +11,7 @@
"name": "IngestionBotRule-Allow",
"description" : "Allow ingestion bots to create/update/delete data entities",
"resources" : ["All"],
- "operations": ["Create", "BulkCreate", "BulkUpdate", "EditAll", "ViewAll", "Delete"],
+ "operations": ["Create", "BulkCreate", "BulkUpdate", "EditAll", "ViewAll", "Delete", "Trigger"],
"effect": "allow"
},
{
diff --git a/openmetadata-service/src/main/resources/json/data/policy/LineageBotPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/LineageBotPolicy.json
index 54e26cd92c30..4028df985789 100644
--- a/openmetadata-service/src/main/resources/json/data/policy/LineageBotPolicy.json
+++ b/openmetadata-service/src/main/resources/json/data/policy/LineageBotPolicy.json
@@ -18,7 +18,7 @@
"name": "LineageBotRule-Allow",
"description" : "Allow creating and updating lineage",
"resources" : ["All"],
- "operations": ["EditAll", "ViewAll"],
+ "operations": ["EditAll", "ViewAll", "Trigger"],
"effect": "allow"
},
{
diff --git a/openmetadata-service/src/main/resources/json/data/policy/ProfilerBotPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/ProfilerBotPolicy.json
index 042b882d47d9..f723c5d7826d 100644
--- a/openmetadata-service/src/main/resources/json/data/policy/ProfilerBotPolicy.json
+++ b/openmetadata-service/src/main/resources/json/data/policy/ProfilerBotPolicy.json
@@ -11,7 +11,7 @@
"name": "ProfilerBotBotRule-Allow",
"description" : "Allow updating sample data, profile data, and tests for all the resources.",
"resources" : ["All"],
- "operations": ["EditAll", "ViewAll"],
+ "operations": ["EditAll", "ViewAll", "Trigger"],
"effect": "allow"
},
{
diff --git a/openmetadata-service/src/main/resources/json/data/policy/QualityBotPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/QualityBotPolicy.json
index 1ef5c06808c9..dc1d8cc04c90 100644
--- a/openmetadata-service/src/main/resources/json/data/policy/QualityBotPolicy.json
+++ b/openmetadata-service/src/main/resources/json/data/policy/QualityBotPolicy.json
@@ -11,7 +11,7 @@
"name": "QualityBotBotRule-Allow",
"description" : "Allow updating sample data, profile data, and tests for all the resources.",
"resources" : ["All"],
- "operations": ["EditAll", "ViewAll"],
+ "operations": ["EditAll", "ViewAll", "Trigger"],
"effect": "allow"
},
{
diff --git a/openmetadata-service/src/main/resources/json/data/policy/UsageBotPolicy.json b/openmetadata-service/src/main/resources/json/data/policy/UsageBotPolicy.json
index 993c02e99a9e..f8400ea4d2c5 100644
--- a/openmetadata-service/src/main/resources/json/data/policy/UsageBotPolicy.json
+++ b/openmetadata-service/src/main/resources/json/data/policy/UsageBotPolicy.json
@@ -18,7 +18,7 @@
"name": "UsageBotRule-Allow-Usage",
"description" : "Allow handling usage and lifecycle information.",
"resources" : ["All"],
- "operations": ["EditAll", "ViewAll"],
+ "operations": ["EditAll", "ViewAll", "Trigger"],
"effect": "allow"
},
{