blob: 5bdf51e2d796f2a2404ed491b9b311c81eca63f8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#include <cstddef>
#include <functional>
#include <memory>
#include <ostream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include <gtest/gtest_prod.h>
#include "kudu/gutil/macros.h"
#include "kudu/gutil/ref_counted.h"
#include "kudu/sentry/sentry_action.h"
#include "kudu/sentry/sentry_authorizable_scope.h"
#include "kudu/sentry/sentry_client.h"
#include "kudu/thrift/client.h"
#include "kudu/util/bitset.h"
#include "kudu/util/locks.h"
#include "kudu/util/metrics.h"
#include "kudu/util/status.h"
#include "kudu/util/status_callback.h"
#include "kudu/util/ttl_cache.h"
namespace sentry {
class TListSentryPrivilegesResponse;
class TSentryAuthorizable;
class TSentryPrivilege;
} // namespace sentry
namespace kudu {
namespace master {
enum SentryCaching {
ALL,
SERVER_AND_DB_ONLY,
};
// Utility struct to facilitate evaluating the privileges of a given
// authorizable. This is preferred to using Sentry's Thrift responses directly,
// since useful information has already been parsed to generate this struct
// (e.g. the SentryActions and scope).
// The 'server' field is omitted: everything is implicitly bound to a particular
// Sentry instance which is the only authoritative source of authz information
// for Kudu in the current model of AuthzProvider.
struct AuthorizablePrivileges {
AuthorizablePrivileges(sentry::SentryAuthorizableScope::Scope scope,
std::string db,
std::string table,
std::string column)
: all_with_grant(false),
scope(scope),
db_name(std::move(db)),
table_name(std::move(table)),
column_name(std::move(column)) {
#ifndef NDEBUG
switch (scope) {
case sentry::SentryAuthorizableScope::COLUMN:
CHECK(!column_name.empty());
FALLTHROUGH_INTENDED;
case sentry::SentryAuthorizableScope::TABLE:
CHECK(!table_name.empty());
FALLTHROUGH_INTENDED;
case sentry::SentryAuthorizableScope::DATABASE:
CHECK(!db_name.empty());
break;
case sentry::SentryAuthorizableScope::SERVER:
break;
default:
LOG(FATAL) << "not reachable";
}
#endif
}
// Whether the privilege 'ALL' or 'OWNER' has been granted with Sentry's
// grant option enabled. Note that the grant option can be granted on any
// action, but for Kudu, we only use it with 'ALL' or 'OWNER'.
bool all_with_grant;
// The scope of the authorizable being granted the privileges.
sentry::SentryAuthorizableScope::Scope scope;
// The set of actions for which privileges are granted.
sentry::SentryActionsSet allowed_actions;
// The fields of the authorizable.
std::string db_name;
std::string table_name;
std::string column_name;
};
// A representation of the Sentry privilege hierarchy branch for a single table
// (including privileges for the table's ancestors and descendents in the
// authz scope hierarchy) for a single user.
class SentryPrivilegesBranch {
public:
// Construct an empty instance: no information on privileges.
SentryPrivilegesBranch() = default;
// Construct an instance for the specified 'authorizable' from 'response'.
SentryPrivilegesBranch(
const ::sentry::TSentryAuthorizable& authorizable,
const ::sentry::TListSentryPrivilegesResponse& response);
// Accessor to the privileges information stored in the object.
const std::vector<AuthorizablePrivileges>& privileges() const {
return privileges_;
}
// Get estimation on amount of memory used (in bytes) to store this instance.
size_t memory_footprint() const;
// Add/merge privileges from other instance of SentryPrivilegesBranch.
void Merge(const SentryPrivilegesBranch& other);
// Output the privileges into branches corresponding to DB-and-higher and
// TABLE-and-lower authz scopes.
void Split(SentryPrivilegesBranch* other_scope_db,
SentryPrivilegesBranch* other_scope_table) const;
private:
// Utility function.
void DoInit(const ::sentry::TSentryAuthorizable& authorizable,
const ::sentry::TListSentryPrivilegesResponse& response);
// Set of granted privileges.
std::vector<AuthorizablePrivileges> privileges_;
};
// A utility class to use in SentryAuthzProvider. This class provides an
// interface for finding privileges granted to a user at some authz scope.
// The authoritative source of the authz privileges information is Sentry,
// where the Sentry-related parameters are specified via command line flags for
// kudu-master binary (see the .cc file for available command line flags).
//
// Optionally, the fetcher can use TTL-based cache to store information
// retrieved from Sentry, making it possible to reuse once fetched information
// until corresponding cache entries expire.
class SentryPrivilegesFetcher {
public:
explicit SentryPrivilegesFetcher(scoped_refptr<MetricEntity> metric_entity);
~SentryPrivilegesFetcher() = default;
// Start/stop the underlying Sentry client.
Status Start();
void Stop();
// Resets the authz cache. In addition to lifecycle-related methods like
// Start(), this method is also used by SentryAuthzProvider::ResetCache().
Status ResetCache();
// Fetches the user's privileges from Sentry for the authorizable specified
// by the given table and scope. The result privileges might be served
// from the cache, if caching is enabled and corresponding entry exists
// in the cache.
//
// If 'caching' is SERVER_AND_DB_ONLY and the SentryPrivilegesFetcher is
// configured to cache privileges, it will not cache privileges equal to or
// below the 'TABLE' scope.
Status GetSentryPrivileges(
sentry::SentryAuthorizableScope::Scope requested_scope,
const std::string& table_ident,
const std::string& user,
SentryCaching caching,
SentryPrivilegesBranch* privileges);
private:
friend class SentryAuthzProviderFilterPrivilegesTest;
friend class SentryAuthzProviderTest;
friend class SentryPrivilegesBranch;
FRIEND_TEST(SentryPrivilegesFetcherStaticTest, TestPrivilegesWellFormed);
FRIEND_TEST(SentryAuthzProviderTest, CacheBehaviorNotCachingTableInfo);
// Utility function to determine whether the given privilege is a well-formed
// possibly Kudu-related privilege describing a descendent or ancestor of the
// requested authorizable in the Sentry hierarchy tree, i.e. it:
// - has a Kudu-related action (e.g. ALL, INSERT, UPDATE, etc.),
// - has a Kudu-related authorizable scope (e.g. SERVER, DATABASE, etc.),
// - all fields of equal or higher scope to the privilege's scope are set;
// none lower are set, and
// - all fields that are set match those set by the input authorizable.
static bool SentryPrivilegeIsWellFormed(
const ::sentry::TSentryPrivilege& privilege,
const ::sentry::TSentryAuthorizable& requested_authorizable,
sentry::SentryAuthorizableScope::Scope* scope,
sentry::SentryAction::Action* action);
// Returns the set of scope fields expected to be non-empty in a Sentry
// response with the given authorizable scope. All fields of equal or higher
// scope are expected to be set.
static const sentry::AuthorizableScopesSet& ExpectedNonEmptyFields(
sentry::SentryAuthorizableScope::Scope scope);
// Returns the set of scope fields expected to be empty in a Sentry response
// with the given authorizable scope. All fields of lower scope are expected
// to be empty.
static const sentry::AuthorizableScopesSet& ExpectedEmptyFields(
sentry::SentryAuthorizableScope::Scope scope);
// Sends a request to fetch privileges from Sentry for the given authorizable.
Status FetchPrivilegesFromSentry(
const std::string& service_name,
const std::string& user,
const ::sentry::TSentryAuthorizable& authorizable,
SentryPrivilegesBranch* result);
// Metric entity for registering metric gauges/counters.
scoped_refptr<MetricEntity> metric_entity_;
// Client instance to communicate with Sentry.
thrift::HaClient<sentry::SentryClient> sentry_client_;
// The TTL cache to store information on privileges received from Sentry.
// The instance is wrapped into std::shared_ptr to handle operations with
// cache items along with concurrent requests to reset the instance.
typedef TTLCache<std::string, SentryPrivilegesBranch> PrivilegeCache;
std::shared_ptr<PrivilegeCache> cache_;
// Synchronization primitive to guard access to the cache in the presence
// of operations with cache items and concurrent requests to reset the cache.
// An alternative would be to use std::atomic_load and std::atomic_exchange,
// but:
// * They're higher overhead operations than just using a spinlock.
// * They're not available in all C++11-compatible compilers.
rw_spinlock cache_lock_;
// Utility dictionary to keep track of requests sent to Sentry. Access is
// guarded by pending_requests_lock_. The key corresponds to the set of
// parameters for a request sent to Sentry.
struct SentryRequestsInfo {
std::vector<StatusCallback> callbacks;
std::shared_ptr<SentryPrivilegesBranch> result;
};
std::unordered_map<std::string, SentryRequestsInfo> pending_requests_;
simple_spinlock pending_requests_lock_;
};
} // namespace master
} // namespace kudu