This is an automated email from the ASF dual-hosted git repository.

abukor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git


The following commit(s) were added to refs/heads/master by this push:
     new 6f0e858  KUDU-3090 Restrict changing ownership of a table
6f0e858 is described below

commit 6f0e858ba929deacf297a116e1d99a678a3d231c
Author: Attila Bukor <[email protected]>
AuthorDate: Fri Jul 10 16:05:54 2020 +0200

    KUDU-3090 Restrict changing ownership of a table
    
    Before this patch changing the ownership required ALTER permissions on a
    table. This patch adds a new method to authorization provider to
    authorize ownership changes and makes catalog manager call it if the
    owner is changed on an alter.
    
    Change-Id: I75a8b24364572a84f93826ad670c543abd407bb1
    Reviewed-on: http://gerrit.cloudera.org:8080/16113
    Reviewed-by: Andrew Wong <[email protected]>
    Tested-by: Kudu Jenkins
---
 src/kudu/integration-tests/master_authz-itest.cc | 70 ++++++++++++++++++++++++
 src/kudu/master/authz_provider.h                 | 10 +++-
 src/kudu/master/catalog_manager.cc               | 12 +++-
 src/kudu/master/default_authz_provider.h         |  6 ++
 src/kudu/master/ranger_authz_provider.cc         | 24 ++++++++
 src/kudu/master/ranger_authz_provider.h          |  4 ++
 6 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/src/kudu/integration-tests/master_authz-itest.cc 
b/src/kudu/integration-tests/master_authz-itest.cc
index 9cbbd53..63d6050 100644
--- a/src/kudu/integration-tests/master_authz-itest.cc
+++ b/src/kudu/integration-tests/master_authz-itest.cc
@@ -190,6 +190,14 @@ class MasterAuthzITestHarness {
     return alterer->DropColumn("int32_val")->Alter();
   }
 
+  static Status ChangeOwner(const OperationParams& p,
+                            const shared_ptr<KuduClient>& client) {
+    unique_ptr<KuduTableAlterer> alterer(
+        client->NewTableAlterer(p.table_name));
+
+    return alterer->SetOwner(kTestUser)->Alter();
+  }
+
   static Status IsAlterTableDone(const OperationParams& p,
                                  const shared_ptr<KuduClient>& client) {
     bool in_progress = false;
@@ -328,6 +336,9 @@ class MasterAuthzITestHarness {
                                          const 
unique_ptr<ExternalMiniCluster>& cluster) = 0;
   virtual Status GrantAlterTablePrivilege(const PrivilegeParams& p,
                                           const 
unique_ptr<ExternalMiniCluster>& cluster) = 0;
+  virtual Status GrantAlterWithGrantTablePrivilege(
+      const PrivilegeParams& p,
+      const unique_ptr<ExternalMiniCluster>& cluster) = 0;
   virtual Status GrantRenameTablePrivilege(const PrivilegeParams& p,
                                            const 
unique_ptr<ExternalMiniCluster>& cluster) = 0;
   virtual Status GrantGetMetadataTablePrivilege(const PrivilegeParams& p,
@@ -411,6 +422,17 @@ class RangerITestHarness : public MasterAuthzITestHarness {
     return RefreshAuthzPolicies(cluster);
   }
 
+  Status GrantAlterWithGrantTablePrivilege(
+      const PrivilegeParams& p,
+      const unique_ptr<ExternalMiniCluster>& cluster) override {
+    AuthorizationPolicy policy;
+    policy.databases.emplace_back(p.db_name);
+    policy.tables.emplace_back(p.table_name);
+    policy.items.emplace_back(PolicyItem({p.user_name}, {ActionPB::ALTER}, 
true));
+    RETURN_NOT_OK(ranger_->AddPolicy(move(policy)));
+    return RefreshAuthzPolicies(cluster);
+  }
+
   Status GrantRenameTablePrivilege(const PrivilegeParams& p,
                                    const unique_ptr<ExternalMiniCluster>& 
cluster) override {
     AuthorizationPolicy policy_new_table;
@@ -604,6 +626,10 @@ class MasterAuthzITestBase : public 
ExternalMiniClusterITestBase {
     return harness_->GrantGetMetadataTablePrivilege(p, cluster_);
   }
 
+  Status GrantAlterWithGrantTablePrivilege(const PrivilegeParams& p) {
+    return harness_->GrantAlterWithGrantTablePrivilege(p, cluster_);
+  }
+
   Status GrantAllTablePrivilege(const PrivilegeParams& p) {
     return harness_->GrantAllTablePrivilege(p, cluster_);
   }
@@ -644,6 +670,10 @@ class MasterAuthzITestBase : public 
ExternalMiniClusterITestBase {
     return harness_->IsAlterTableDone(p, client_);
   }
 
+  Status ChangeOwner(const OperationParams& p) {
+    return harness_->ChangeOwner(p, client_);
+  }
+
   Status RenameTable(const OperationParams& p) {
     return harness_->RenameTable(p, client_);
   }
@@ -889,6 +919,37 @@ TEST_P(MasterAuthzITest, TestAuthzGiveAwayOwnership) {
   }
 }
 
+TEST_P(MasterAuthzITest, TestChangeOwnerWithoutDelegateAdmin) {
+  this->GrantAllDatabasePrivilege({kDatabaseName, kTableName});
+  const string table_name = Substitute("$0.$1", kDatabaseName, kTableName);
+
+  unique_ptr<KuduTableAlterer> 
alterer(this->client_->NewTableAlterer(table_name));
+  Status s = alterer->SetOwner(kSecondUser)->Alter();
+  ASSERT_TRUE(s.IsNotAuthorized());
+}
+
+TEST_P(MasterAuthzITest, TestChangeOwnerWithoutAll) {
+  this->GrantAlterWithGrantTablePrivilege({kDatabaseName, kTableName});
+  const string table_name = Substitute("$0.$1", kDatabaseName, kTableName);
+
+  unique_ptr<KuduTableAlterer> 
alterer(this->client_->NewTableAlterer(table_name));
+  Status s = alterer->SetOwner(kSecondUser)->Alter();
+  ASSERT_TRUE(s.IsNotAuthorized());
+}
+
+TEST_P(MasterAuthzITest, TestAlterAndChangeOwner) {
+  this->GrantAlterTablePrivilege({kDatabaseName, kTableName});
+  const string table_name = Substitute("$0.$1", kDatabaseName, kTableName);
+
+  unique_ptr<KuduTableAlterer> 
alterer(this->client_->NewTableAlterer(table_name));
+  alterer->SetOwner(kSecondUser)->DropColumn("int8_val");
+  Status s = alterer->Alter();
+  ASSERT_TRUE(s.IsNotAuthorized());
+
+  this->GrantAllWithGrantTablePrivilege({kDatabaseName, kTableName});
+  ASSERT_OK(alterer->Alter());
+}
+
 class MasterAuthzOwnerITest : public MasterAuthzITestBase,
                               public 
::testing::WithParamInterface<std::tuple<HarnessEnum,
                                                                               
std::string>> {
@@ -1113,6 +1174,15 @@ static const AuthzDescriptor kAuthzCombinations[] = {
       kTableName,
       ""
     },
+    {
+      {
+        &MasterAuthzITestBase::ChangeOwner,
+        &MasterAuthzITestBase::GrantAllWithGrantTablePrivilege,
+        "ChangeOwner",
+      },
+      kDatabaseName,
+      kTableName,
+    },
 };
 INSTANTIATE_TEST_CASE_P(
     AuthzCombinations, TestAuthzTable,
diff --git a/src/kudu/master/authz_provider.h b/src/kudu/master/authz_provider.h
index 1c98b15..401b4ed 100644
--- a/src/kudu/master/authz_provider.h
+++ b/src/kudu/master/authz_provider.h
@@ -33,7 +33,6 @@ class TablePrivilegePB;
 } // namespace security
 
 namespace master {
-
 // An interface for handling authorizations on Kudu operations.
 class AuthzProvider {
  public:
@@ -113,6 +112,15 @@ class AuthzProvider {
                                       const SchemaPB& schema_pb,
                                       security::TablePrivilegePB* pb) 
WARN_UNUSED_RESULT = 0;
 
+  // Checks if changing the owner of the table is authorized for the given 
user.
+  // 'is_owner' indicates whether 'user' is the current owner of the table.
+  //
+  // If the operation is not authorized, returns Status::NotAuthorized().
+  // Otherwise, may return other Status error codes depend on actual errors.
+  virtual Status AuthorizeChangeOwner(const std::string& table_name,
+                                      const std::string& user,
+                                      bool is_owner) WARN_UNUSED_RESULT = 0;
+
   // Refreshes policies in the authorization provider plugin.
   virtual Status RefreshPolicies() WARN_UNUSED_RESULT = 0;
 
diff --git a/src/kudu/master/catalog_manager.cc 
b/src/kudu/master/catalog_manager.cc
index 706346b..96f687e 100644
--- a/src/kudu/master/catalog_manager.cc
+++ b/src/kudu/master/catalog_manager.cc
@@ -2612,10 +2612,19 @@ Status CatalogManager::AlterTable(const 
AlterTableRequestPB& req,
                          const string& owner) {
     const string new_table = req.has_new_table_name() ?
         NormalizeTableName(req.new_table_name()) : table_name;
+    // Change owner requires higher level of privilege (ALL WITH GRANT OPTION,
+    // or ALL + delegate admin) than other types of alter operations, so if a
+    // single alter contains an owner change as well as other changes, it's
+    // sufficient to authorize only the owner change.
+    if (req.has_new_table_owner()) {
+      return SetupError(authz_provider_->AuthorizeChangeOwner(table_name, 
username,
+                                                              username == 
owner),
+                        resp, MasterErrorPB::NOT_AUTHORIZED);
+    }
+
     return SetupError(authz_provider_->AuthorizeAlterTable(table_name, 
new_table, username,
                                                            username == owner),
                       resp, MasterErrorPB::NOT_AUTHORIZED);
-
   };
   RETURN_NOT_OK(FindLockAndAuthorizeTable(req, resp, LockMode::WRITE, 
authz_func, user,
                                           &table, &l));
@@ -2701,7 +2710,6 @@ Status CatalogManager::AlterTable(const 
AlterTableRequestPB& req,
   });
 
   // 5. Alter the table owner.
-  // TODO(abukor): Only the owner (or superuser) can alter the owner?
   if (req.has_new_table_owner()) {
     RETURN_NOT_OK(SetupError(
           ValidateOwner(req.new_table_owner()).CloneAndAppend("invalid owner 
name"),
diff --git a/src/kudu/master/default_authz_provider.h 
b/src/kudu/master/default_authz_provider.h
index 84da23e..2e8554c 100644
--- a/src/kudu/master/default_authz_provider.h
+++ b/src/kudu/master/default_authz_provider.h
@@ -78,6 +78,12 @@ class DefaultAuthzProvider : public AuthzProvider {
     return Status::OK();
   }
 
+  Status AuthorizeChangeOwner(const std::string& /*table_name*/,
+                              const std::string& /*user*/,
+                              bool /*is_owner*/) override WARN_UNUSED_RESULT {
+    return Status::OK();
+  }
+
   Status FillTablePrivilegePB(const std::string& /*table_name*/,
                               const std::string& /*user*/,
                               bool /*is_owner*/,
diff --git a/src/kudu/master/ranger_authz_provider.cc 
b/src/kudu/master/ranger_authz_provider.cc
index 25cdd06..156cef4 100644
--- a/src/kudu/master/ranger_authz_provider.cc
+++ b/src/kudu/master/ranger_authz_provider.cc
@@ -345,6 +345,30 @@ Status RangerAuthzProvider::RefreshPolicies() {
   return client_.RefreshPolicies();
 }
 
+Status RangerAuthzProvider::AuthorizeChangeOwner(const string& table_name,
+                                                 const string& user,
+                                                 bool is_owner) {
+  if (IsTrustedUser(user)) {
+    return Status::OK();
+  }
+
+  string db;
+  string tbl;
+
+  RETURN_NOT_OK(ParseTableIdentifier(table_name, &db, &tbl));
+
+  bool authorized;
+  RETURN_NOT_OK(client_.AuthorizeAction(user, ActionPB::ALL, db, tbl, is_owner,
+                                        /*requires_delegate_admin=*/true, 
&authorized));
+
+  if (PREDICT_FALSE(!authorized)) {
+    LOG(WARNING) << Substitute("User $0 is not authorized to change owner of 
$1", user, table_name);
+    return Status::NotAuthorized(kUnauthorizedAction);
+  }
+
+  return Status::OK();
+}
+
 bool RangerAuthzProvider::IsEnabled() {
   return !FLAGS_ranger_config_path.empty();
 }
diff --git a/src/kudu/master/ranger_authz_provider.h 
b/src/kudu/master/ranger_authz_provider.h
index 8966f7b..973acb5 100644
--- a/src/kudu/master/ranger_authz_provider.h
+++ b/src/kudu/master/ranger_authz_provider.h
@@ -81,6 +81,10 @@ class RangerAuthzProvider : public AuthzProvider {
                               const SchemaPB& schema_pb,
                               security::TablePrivilegePB* pb) override 
WARN_UNUSED_RESULT;
 
+  Status AuthorizeChangeOwner(const std::string& table_name,
+                              const std::string& user,
+                              bool is_owner) override WARN_UNUSED_RESULT;
+
   Status RefreshPolicies() override WARN_UNUSED_RESULT;
 
   // Returns true if 'ranger_policy_server' flag is set indicating Ranger

Reply via email to