Copilot commented on code in PR #6884:
URL: https://github.com/apache/hive/pull/6884#discussion_r4407697799


##########
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/BaseHandler.java:
##########
@@ -874,6 +874,28 @@ private void authorizeProxyPrivilege() throws TException {
     }
   }
 
+  /**
+   * Tells whether the caller may be treated as a trusted proxy/operator (e.g. 
HiveServer2 or the compactor),
+   * using the same rules as {@link #authorizeProxyPrivilege()} but without 
failing the call: the caller is
+   * trusted in embedded mode, when the proxy check is disabled, in test mode, 
or when the user has host-proxy
+   * privileges.
+   */
+  protected boolean hasProxyPrivilege() throws TException {
+    if (!HiveMetaStore.isMetaStoreRemote() ||
+        !MetastoreConf.getBoolVar(conf, 
MetastoreConf.ConfVars.EVENT_DB_NOTIFICATION_API_AUTH) ||
+        conf.getBoolean(HIVE_IN_TEST.getVarname(), false)) {

Review Comment:
   Disabling `metastore.event.db.notification.api.auth`, documented as 
notification-only, now lets every remote caller bypass table authorization, 
run-as derivation, and cleanup-path validation in both maintenance APIs. Remove 
this setting from the trust decision so notification access does not also grant 
compaction and cleanup privileges. This is VALID under THREAT_MODEL.md §13: 
direct HMS clients are in scope (§3.3), and configured authorization must be 
enforced (§5).



##########
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/BaseHandler.java:
##########
@@ -874,6 +874,28 @@ private void authorizeProxyPrivilege() throws TException {
     }
   }
 
+  /**
+   * Tells whether the caller may be treated as a trusted proxy/operator (e.g. 
HiveServer2 or the compactor),
+   * using the same rules as {@link #authorizeProxyPrivilege()} but without 
failing the call: the caller is
+   * trusted in embedded mode, when the proxy check is disabled, in test mode, 
or when the user has host-proxy
+   * privileges.
+   */
+  protected boolean hasProxyPrivilege() throws TException {
+    if (!HiveMetaStore.isMetaStoreRemote() ||
+        !MetastoreConf.getBoolVar(conf, 
MetastoreConf.ConfVars.EVENT_DB_NOTIFICATION_API_AUTH) ||
+        conf.getBoolean(HIVE_IN_TEST.getVarname(), false)) {
+      return true;
+    }
+    String user;
+    try {
+      user = SecurityUtils.getUGI().getShortUserName();

Review Comment:
   For SASL PLAIN clients on Kerberized Hadoop, the transport records 
`remoteUser` but invokes the handler without `doAs`, so this check uses the HMS 
service identity and can bypass authorization and request validation if that 
identity has proxy-host privileges. Use the authenticated transport identity, 
fail closed when it is unavailable, and add a non-proxy PLAIN-client regression 
test. This is VALID under THREAT_MODEL.md §13 because direct HMS clients are in 
scope (§3.3) and authorization must use the authenticated caller (§5).



##########
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/TransactionHandler.java:
##########
@@ -505,9 +524,87 @@ public ShowCompactResponse show_compact(ShowCompactRequest 
rqst) throws TExcepti
   @Override
   public boolean submit_for_cleanup(CompactionRequest rqst, long 
highestWriteId, long txnId)
       throws TException {
+    if (!hasProxyPrivilege()) {
+      try {
+        authorizeAndPrepareCompactionRequest(rqst);
+      } catch (NoSuchObjectException e) {
+        // The table/partition does not exist (anymore): there is nothing an 
untrusted caller may clean up.
+        LOG.info("Ignoring the cleanup request of {}.{}: {}", 
rqst.getDbname(), rqst.getTablename(),
+            e.getMessage());
+        return false;
+      } catch (Exception e) {
+        rethrowException(e);
+      }
+    }
     return getTxnHandler().submitForCleanup(rqst, highestWriteId, txnId);
   }
 
+  /**
+   * Authorize a compaction/cleanup request of a caller that has no proxy 
privilege and derive the
+   * server-side request attributes: the table is authorized with a {@link 
PreAlterTableEvent}, the
+   * canonical db/table names are used for the queued request, the run-as user 
is derived from the
+   * storage location and a caller-supplied cleanup location must stay inside 
the table/partition location.
+   *
+   * @throws NoSuchObjectException if the table or the partition does not exist
+   */
+  private void authorizeAndPrepareCompactionRequest(CompactionRequest rqst) 
throws TException {
+    if (rqst.getDbname() == null || rqst.getTablename() == null) {
+      throw new MetaException("The DB and table name cannot be null.");
+    }
+    String[] parsedDbName = parseDbName(rqst.getDbname(), conf);
+    String catName = parsedDbName[CAT_NAME];
+    String dbName = parsedDbName[DB_NAME];
+    if (!getDefaultCatalog(conf).equalsIgnoreCase(catName)) {
+      // the compaction queue has no notion of catalogs, so the request would 
end up on the default catalog
+      throw new MetaException("Compaction and cleanup requests are only 
supported for the default catalog, got "
+          + catName);
+    }
+
+    GetTableRequest getTableRequest = new GetTableRequest(dbName, 
rqst.getTablename());
+    getTableRequest.setCatName(catName);
+    Table table = get_table_core(getTableRequest);
+    firePreEvent(new PreAlterTableEvent(table, table, this));

Review Comment:
   Passing the same `Table` as both old and new causes the automatically 
installed `TransactionalValidationListener` to mistake existing full-ACID 
tables for conversions: it removes `transactional` from the new parameters 
before reading the old value. For tables containing normal `bucket_*` data 
files, conversion validation then rejects the compaction or cleanup request 
before queueing. Pass a deep copy as the new table to preserve the old state.



##########
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/TransactionHandler.java:
##########
@@ -505,9 +524,87 @@ public ShowCompactResponse show_compact(ShowCompactRequest 
rqst) throws TExcepti
   @Override
   public boolean submit_for_cleanup(CompactionRequest rqst, long 
highestWriteId, long txnId)
       throws TException {
+    if (!hasProxyPrivilege()) {
+      try {
+        authorizeAndPrepareCompactionRequest(rqst);
+      } catch (NoSuchObjectException e) {
+        // The table/partition does not exist (anymore): there is nothing an 
untrusted caller may clean up.
+        LOG.info("Ignoring the cleanup request of {}.{}: {}", 
rqst.getDbname(), rqst.getTablename(),
+            e.getMessage());
+        return false;
+      } catch (Exception e) {
+        rethrowException(e);
+      }
+    }
     return getTxnHandler().submitForCleanup(rqst, highestWriteId, txnId);
   }
 
+  /**
+   * Authorize a compaction/cleanup request of a caller that has no proxy 
privilege and derive the
+   * server-side request attributes: the table is authorized with a {@link 
PreAlterTableEvent}, the
+   * canonical db/table names are used for the queued request, the run-as user 
is derived from the
+   * storage location and a caller-supplied cleanup location must stay inside 
the table/partition location.
+   *
+   * @throws NoSuchObjectException if the table or the partition does not exist
+   */
+  private void authorizeAndPrepareCompactionRequest(CompactionRequest rqst) 
throws TException {
+    if (rqst.getDbname() == null || rqst.getTablename() == null) {
+      throw new MetaException("The DB and table name cannot be null.");
+    }
+    String[] parsedDbName = parseDbName(rqst.getDbname(), conf);
+    String catName = parsedDbName[CAT_NAME];
+    String dbName = parsedDbName[DB_NAME];
+    if (!getDefaultCatalog(conf).equalsIgnoreCase(catName)) {
+      // the compaction queue has no notion of catalogs, so the request would 
end up on the default catalog
+      throw new MetaException("Compaction and cleanup requests are only 
supported for the default catalog, got "
+          + catName);
+    }
+
+    GetTableRequest getTableRequest = new GetTableRequest(dbName, 
rqst.getTablename());
+    getTableRequest.setCatName(catName);
+    Table table = get_table_core(getTableRequest);
+    firePreEvent(new PreAlterTableEvent(table, table, this));
+
+    // queue the request for the table that has been authorized
+    rqst.setDbname(table.getDbName());
+    rqst.setTablename(table.getTableName());
+
+    String location = table.getSd() == null ? null : 
table.getSd().getLocation();
+    if (rqst.isSetPartitionname() && rqst.getPartitionname() != null) {
+      List<String> partVals = getPartValsFromName(table, 
rqst.getPartitionname());
+      Partition partition = getMS().getPartition(catName, table.getDbName(), 
table.getTableName(), partVals);

Review Comment:
   Authorized non-proxy callers can no longer submit partition-scoped Iceberg 
compactions because this lookup requires an HMS partition record. Iceberg 
partition names come from storage-handler metadata and are resolved by the 
Iceberg compaction implementation, not HMS. Distinguish native partitions from 
storage-handler partitions, retaining table authorization and using the table 
location for run-as derivation in the latter case.



##########
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/TransactionHandler.java:
##########
@@ -505,9 +524,87 @@ public ShowCompactResponse show_compact(ShowCompactRequest 
rqst) throws TExcepti
   @Override
   public boolean submit_for_cleanup(CompactionRequest rqst, long 
highestWriteId, long txnId)
       throws TException {
+    if (!hasProxyPrivilege()) {
+      try {
+        authorizeAndPrepareCompactionRequest(rqst);
+      } catch (NoSuchObjectException e) {
+        // The table/partition does not exist (anymore): there is nothing an 
untrusted caller may clean up.
+        LOG.info("Ignoring the cleanup request of {}.{}: {}", 
rqst.getDbname(), rqst.getTablename(),
+            e.getMessage());
+        return false;
+      } catch (Exception e) {
+        rethrowException(e);
+      }
+    }
     return getTxnHandler().submitForCleanup(rqst, highestWriteId, txnId);
   }
 
+  /**
+   * Authorize a compaction/cleanup request of a caller that has no proxy 
privilege and derive the
+   * server-side request attributes: the table is authorized with a {@link 
PreAlterTableEvent}, the
+   * canonical db/table names are used for the queued request, the run-as user 
is derived from the
+   * storage location and a caller-supplied cleanup location must stay inside 
the table/partition location.
+   *
+   * @throws NoSuchObjectException if the table or the partition does not exist
+   */
+  private void authorizeAndPrepareCompactionRequest(CompactionRequest rqst) 
throws TException {
+    if (rqst.getDbname() == null || rqst.getTablename() == null) {
+      throw new MetaException("The DB and table name cannot be null.");
+    }
+    String[] parsedDbName = parseDbName(rqst.getDbname(), conf);
+    String catName = parsedDbName[CAT_NAME];
+    String dbName = parsedDbName[DB_NAME];
+    if (!getDefaultCatalog(conf).equalsIgnoreCase(catName)) {
+      // the compaction queue has no notion of catalogs, so the request would 
end up on the default catalog
+      throw new MetaException("Compaction and cleanup requests are only 
supported for the default catalog, got "
+          + catName);
+    }
+
+    GetTableRequest getTableRequest = new GetTableRequest(dbName, 
rqst.getTablename());
+    getTableRequest.setCatName(catName);
+    Table table = get_table_core(getTableRequest);
+    firePreEvent(new PreAlterTableEvent(table, table, this));
+
+    // queue the request for the table that has been authorized
+    rqst.setDbname(table.getDbName());
+    rqst.setTablename(table.getTableName());
+
+    String location = table.getSd() == null ? null : 
table.getSd().getLocation();
+    if (rqst.isSetPartitionname() && rqst.getPartitionname() != null) {
+      List<String> partVals = getPartValsFromName(table, 
rqst.getPartitionname());
+      Partition partition = getMS().getPartition(catName, table.getDbName(), 
table.getTableName(), partVals);

Review Comment:
   This lookup accepts `p=a/extra=b` for an existing partition `p=a`, but 
leaves the original name in the queued request. With a supplied cleanup 
location, the cleaner's later lookup of that raw name can fail and trigger its 
dropped-partition purge path for a live partition. Canonicalize the queued name 
from the fetched partition so validation and execution target the same 
partition.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to