2022-06-03 09:06:31 +00:00
|
|
|
#include <Access/KerberosInit.h>
|
|
|
|
#include <Common/Exception.h>
|
|
|
|
#include <Common/logger_useful.h>
|
|
|
|
#include <Poco/Logger.h>
|
|
|
|
#include <Loggers/Loggers.h>
|
2022-06-03 15:07:18 +00:00
|
|
|
#include <filesystem>
|
2022-06-21 15:55:17 +00:00
|
|
|
#include <boost/core/noncopyable.hpp>
|
2022-06-23 07:28:31 +00:00
|
|
|
#include <fmt/format.h>
|
2022-06-16 09:30:40 +00:00
|
|
|
#if USE_KRB5
|
2022-06-15 17:08:16 +00:00
|
|
|
#include <krb5.h>
|
|
|
|
#include <mutex>
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-08 14:57:45 +00:00
|
|
|
using namespace DB;
|
|
|
|
|
2022-06-15 17:26:42 +00:00
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int KERBEROS_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-22 08:28:00 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct K5Data
|
2022-06-15 14:02:53 +00:00
|
|
|
{
|
|
|
|
krb5_context ctx;
|
2022-06-22 13:31:48 +00:00
|
|
|
krb5_ccache out_cc;
|
2022-06-15 14:02:53 +00:00
|
|
|
krb5_principal me;
|
|
|
|
char * name;
|
|
|
|
krb5_boolean switch_to_cache;
|
|
|
|
};
|
|
|
|
|
2022-06-21 15:55:17 +00:00
|
|
|
/**
|
|
|
|
* This class implements programmatic implementation of kinit.
|
|
|
|
*/
|
|
|
|
class KerberosInit : boost::noncopyable
|
2022-06-15 14:02:53 +00:00
|
|
|
{
|
|
|
|
public:
|
2022-06-23 07:28:31 +00:00
|
|
|
void init(const String & keytab_file, const String & principal, const String & cache_name = "");
|
2022-06-15 14:02:53 +00:00
|
|
|
~KerberosInit();
|
|
|
|
private:
|
2022-06-23 07:28:31 +00:00
|
|
|
struct K5Data k5 {};
|
2022-06-15 14:02:53 +00:00
|
|
|
krb5_ccache defcache = nullptr;
|
|
|
|
krb5_get_init_creds_opt * options = nullptr;
|
2022-06-22 13:31:48 +00:00
|
|
|
// Credentials structure including ticket, session key, and lifetime info.
|
2023-11-07 00:16:38 +00:00
|
|
|
krb5_creds my_creds {};
|
2022-06-15 14:02:53 +00:00
|
|
|
krb5_keytab keytab = nullptr;
|
|
|
|
krb5_principal defcache_princ = nullptr;
|
2022-06-23 13:11:48 +00:00
|
|
|
String fmtError(krb5_error_code code) const;
|
2022-06-15 14:02:53 +00:00
|
|
|
};
|
2022-06-22 08:28:00 +00:00
|
|
|
}
|
2022-06-08 14:57:45 +00:00
|
|
|
|
2022-06-23 07:28:31 +00:00
|
|
|
|
2022-06-23 13:11:48 +00:00
|
|
|
String KerberosInit::fmtError(krb5_error_code code) const
|
2022-06-23 07:28:31 +00:00
|
|
|
{
|
|
|
|
const char *msg;
|
|
|
|
msg = krb5_get_error_message(k5.ctx, code);
|
|
|
|
String fmt_error = fmt::format(" ({}, {})", code, msg);
|
|
|
|
krb5_free_error_message(k5.ctx, msg);
|
|
|
|
return fmt_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KerberosInit::init(const String & keytab_file, const String & principal, const String & cache_name)
|
2022-06-03 09:06:31 +00:00
|
|
|
{
|
2024-01-23 17:04:50 +00:00
|
|
|
auto log = getLogger("KerberosInit");
|
2022-06-23 07:28:31 +00:00
|
|
|
LOG_TRACE(log,"Trying to authenticate with Kerberos v5");
|
2022-06-03 09:06:31 +00:00
|
|
|
|
|
|
|
krb5_error_code ret;
|
|
|
|
|
2022-06-03 15:07:18 +00:00
|
|
|
const char *deftype = nullptr;
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-03 15:07:18 +00:00
|
|
|
if (!std::filesystem::exists(keytab_file))
|
2023-01-23 21:13:58 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Keytab file does not exist");
|
2022-06-03 15:07:18 +00:00
|
|
|
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_init_context(&k5.ctx);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2022-06-23 13:11:48 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error while initializing Kerberos 5 library ({})", ret);
|
2022-06-03 09:06:31 +00:00
|
|
|
|
|
|
|
if (!cache_name.empty())
|
|
|
|
{
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_resolve(k5.ctx, cache_name.c_str(), &k5.out_cc);
|
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error in resolving cache: {}", fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Resolved cache");
|
2022-06-03 09:06:31 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-09 08:51:15 +00:00
|
|
|
// Resolve the default cache and get its type and default principal (if it is initialized).
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_default(k5.ctx, &defcache);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error while getting default cache: {}", fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Resolved default cache");
|
2022-06-08 14:57:45 +00:00
|
|
|
deftype = krb5_cc_get_type(k5.ctx, defcache);
|
|
|
|
if (krb5_cc_get_principal(k5.ctx, defcache, &defcache_princ) != 0)
|
2022-06-03 09:06:31 +00:00
|
|
|
defcache_princ = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use the specified principal name.
|
2022-06-22 13:31:48 +00:00
|
|
|
ret = krb5_parse_name_flags(k5.ctx, principal.c_str(), 0, &k5.me);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error when parsing principal name ({}): {}", principal, fmtError(ret));
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-03 15:07:18 +00:00
|
|
|
// Cache related commands
|
2022-06-08 14:57:45 +00:00
|
|
|
if (k5.out_cc == nullptr && krb5_cc_support_switch(k5.ctx, deftype))
|
2022-06-03 15:07:18 +00:00
|
|
|
{
|
|
|
|
// Use an existing cache for the client principal if we can.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_cache_match(k5.ctx, k5.me, &k5.out_cc);
|
2022-06-03 15:07:18 +00:00
|
|
|
if (ret && ret != KRB5_CC_NOTFOUND)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error while searching for cache for ({}): {}", principal, fmtError(ret));
|
2022-06-23 07:28:31 +00:00
|
|
|
if (0 == ret)
|
2022-06-03 15:07:18 +00:00
|
|
|
{
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Using default cache: {}", krb5_cc_get_name(k5.ctx, k5.out_cc));
|
2022-06-08 14:57:45 +00:00
|
|
|
k5.switch_to_cache = 1;
|
2022-06-03 15:07:18 +00:00
|
|
|
}
|
|
|
|
else if (defcache_princ != nullptr)
|
|
|
|
{
|
|
|
|
// Create a new cache to avoid overwriting the initialized default cache.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_new_unique(k5.ctx, deftype, nullptr, &k5.out_cc);
|
2022-06-03 15:07:18 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error while generating new cache: {}", fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Using default cache: {}", krb5_cc_get_name(k5.ctx, k5.out_cc));
|
2022-06-08 14:57:45 +00:00
|
|
|
k5.switch_to_cache = 1;
|
2022-06-03 15:07:18 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-03 15:07:18 +00:00
|
|
|
// Use the default cache if we haven't picked one yet.
|
2022-06-08 14:57:45 +00:00
|
|
|
if (k5.out_cc == nullptr)
|
2022-06-03 15:07:18 +00:00
|
|
|
{
|
2022-06-08 14:57:45 +00:00
|
|
|
k5.out_cc = defcache;
|
2022-06-03 15:07:18 +00:00
|
|
|
defcache = nullptr;
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Using default cache: {}", krb5_cc_get_name(k5.ctx, k5.out_cc));
|
2022-06-03 15:07:18 +00:00
|
|
|
}
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_unparse_name(k5.ctx, k5.me, &k5.name);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error when unparsing name: {}", fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Using principal: {}", k5.name);
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-22 13:31:48 +00:00
|
|
|
// Allocate a new initial credential options structure.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_get_init_creds_opt_alloc(k5.ctx, &options);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error in options allocation: {}", fmtError(ret));
|
2022-06-08 14:57:45 +00:00
|
|
|
|
2022-06-03 09:06:31 +00:00
|
|
|
// Resolve keytab
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_kt_resolve(k5.ctx, keytab_file.c_str(), &keytab);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error in resolving keytab ({}): {}", keytab_file, fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Using keytab: {}", keytab_file);
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-22 13:31:48 +00:00
|
|
|
// Set an output credential cache in initial credential options.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_get_init_creds_opt_set_out_ccache(k5.ctx, options, k5.out_cc);
|
2022-06-03 15:07:18 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error in setting output credential cache: {}", fmtError(ret));
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-08 14:57:45 +00:00
|
|
|
// Action: init or renew
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Trying to renew credentials");
|
2022-06-22 13:31:48 +00:00
|
|
|
memset(&my_creds, 0, sizeof(my_creds));
|
|
|
|
// Get renewed credential from KDC using an existing credential from output cache.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_get_renewed_creds(k5.ctx, &my_creds, k5.me, k5.out_cc, nullptr);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (ret)
|
2022-06-06 08:34:10 +00:00
|
|
|
{
|
2022-06-23 13:11:48 +00:00
|
|
|
LOG_TRACE(log,"Renew failed {}", fmtError(ret));
|
|
|
|
LOG_TRACE(log,"Trying to get initial credentials");
|
2022-06-22 13:31:48 +00:00
|
|
|
// Request KDC for an initial credentials using keytab.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_get_init_creds_keytab(k5.ctx, &my_creds, k5.me, keytab, 0, nullptr, options);
|
2022-06-06 08:34:10 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error in getting initial credentials: {}", fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log, "Got initial credentials");
|
2022-06-06 08:34:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Successful renewal");
|
2022-06-22 13:31:48 +00:00
|
|
|
// Initialize a credential cache. Destroy any existing contents of cache and initialize it for the default principal.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_initialize(k5.ctx, k5.out_cc, k5.me);
|
2022-06-06 08:34:10 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error when initializing cache: {}", fmtError(ret));
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Initialized cache");
|
2022-06-22 13:31:48 +00:00
|
|
|
// Store credentials in a credential cache.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_store_cred(k5.ctx, k5.out_cc, &my_creds);
|
2022-06-06 08:34:10 +00:00
|
|
|
if (ret)
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Error while storing credentials");
|
|
|
|
LOG_TRACE(log,"Stored credentials");
|
2022-06-06 08:34:10 +00:00
|
|
|
}
|
|
|
|
|
2022-06-15 16:35:21 +00:00
|
|
|
if (k5.switch_to_cache)
|
|
|
|
{
|
2022-06-22 13:31:48 +00:00
|
|
|
// Make a credential cache the primary cache for its collection.
|
2022-06-08 14:57:45 +00:00
|
|
|
ret = krb5_cc_switch(k5.ctx, k5.out_cc);
|
2022-06-06 08:34:10 +00:00
|
|
|
if (ret)
|
2023-01-23 23:46:03 +00:00
|
|
|
throw Exception(ErrorCodes::KERBEROS_ERROR, "Error while switching to new cache: {}", fmtError(ret));
|
2022-06-06 08:34:10 +00:00
|
|
|
}
|
2022-06-03 09:06:31 +00:00
|
|
|
|
2022-06-21 15:55:17 +00:00
|
|
|
LOG_TRACE(log,"Authenticated to Kerberos v5");
|
2022-06-03 09:06:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
KerberosInit::~KerberosInit()
|
|
|
|
{
|
2022-06-08 14:57:45 +00:00
|
|
|
if (k5.ctx)
|
2022-06-03 09:06:31 +00:00
|
|
|
{
|
|
|
|
if (defcache)
|
2022-06-08 14:57:45 +00:00
|
|
|
krb5_cc_close(k5.ctx, defcache);
|
|
|
|
krb5_free_principal(k5.ctx, defcache_princ);
|
|
|
|
|
2022-06-03 09:06:31 +00:00
|
|
|
if (options)
|
2022-06-08 14:57:45 +00:00
|
|
|
krb5_get_init_creds_opt_free(k5.ctx, options);
|
|
|
|
if (my_creds.client == k5.me)
|
2022-06-03 09:06:31 +00:00
|
|
|
my_creds.client = nullptr;
|
2022-06-08 14:57:45 +00:00
|
|
|
krb5_free_cred_contents(k5.ctx, &my_creds);
|
2022-06-03 09:06:31 +00:00
|
|
|
if (keytab)
|
2022-06-08 14:57:45 +00:00
|
|
|
krb5_kt_close(k5.ctx, keytab);
|
|
|
|
|
|
|
|
krb5_free_unparsed_name(k5.ctx, k5.name);
|
|
|
|
krb5_free_principal(k5.ctx, k5.me);
|
|
|
|
if (k5.out_cc != nullptr)
|
|
|
|
krb5_cc_close(k5.ctx, k5.out_cc);
|
|
|
|
krb5_free_context(k5.ctx);
|
2022-06-03 09:06:31 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-15 14:02:53 +00:00
|
|
|
|
2022-06-23 07:28:31 +00:00
|
|
|
void kerberosInit(const String & keytab_file, const String & principal, const String & cache_name)
|
2022-06-15 14:02:53 +00:00
|
|
|
{
|
|
|
|
// Using mutex to prevent cache file corruptions
|
|
|
|
static std::mutex kinit_mtx;
|
2022-06-28 19:19:06 +00:00
|
|
|
std::lock_guard lck(kinit_mtx);
|
2022-06-15 14:02:53 +00:00
|
|
|
KerberosInit k_init;
|
2022-06-23 07:28:31 +00:00
|
|
|
k_init.init(keytab_file, principal, cache_name);
|
2022-06-15 14:02:53 +00:00
|
|
|
}
|
2022-06-16 09:30:40 +00:00
|
|
|
#endif // USE_KRB5
|