19 #include <boost/algorithm/string.hpp>
20 #include <boost/thread/thread.hpp>
21 #include <boost/scoped_ptr.hpp>
22 #include <boost/random/mersenne_twister.hpp>
23 #include <boost/random/uniform_int.hpp>
24 #include <boost/filesystem.hpp>
25 #include <gutil/strings/substitute.h>
28 #include <thrift/Thrift.h>
31 #include <glog/logging.h>
32 #include <gflags/gflags.h>
46 #include <sys/types.h>
52 using boost::algorithm::is_any_of;
53 using boost::algorithm::replace_all;
54 using boost::algorithm::split;
56 using boost::uniform_int;
57 using namespace apache::thrift;
58 using namespace boost::filesystem;
59 using namespace strings;
68 "Interval, in minutes, between kerberos ticket renewals. Each renewal will request "
69 "a ticket with a lifetime that is at least 2x the renewal interval.");
70 DEFINE_string(sasl_path,
"/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:"
71 "/usr/lib/x86_64-linux-gnu/sasl2",
"Colon separated list of paths to look for SASL "
72 "security library plugins.");
74 "If true, use LDAP authentication for client connections");
76 DEFINE_string(ldap_uri,
"",
"The URI of the LDAP server to authenticate users against");
77 DEFINE_bool(ldap_tls,
false,
"If true, use the secure TLS protocol to connect to the LDAP"
79 DEFINE_string(ldap_ca_certificate,
"",
"The full path to the certificate file used to"
80 " authenticate the LDAP server's certificate for SSL / TLS connections.");
81 DEFINE_bool(ldap_passwords_in_clear_ok,
false,
"If set, will allow LDAP passwords "
82 "to be sent in the clear (without TLS/SSL) over the network. This option should not "
83 "be used in production environments" );
84 DEFINE_bool(ldap_allow_anonymous_binds,
false,
"(Advanced) If true, LDAP authentication "
85 "with a blank password (an 'anonymous bind') is allowed by Impala.");
86 DEFINE_bool(ldap_manual_config,
false,
"Obsolete; Ignored");
87 DEFINE_string(ldap_domain,
"",
"If set, Impala will try to bind to LDAP with a name of "
88 "the form <userid>@<ldap_domain>");
89 DEFINE_string(ldap_baseDN,
"",
"If set, Impala will try to bind to LDAP with a name of "
90 "the form uid=<userid>,<ldap_baseDN>");
91 DEFINE_string(ldap_bind_pattern,
"",
"If set, Impala will try to bind to LDAP with a name"
92 " of <ldap_bind_pattern>, but where the string #UID is replaced by the user ID. Use"
93 " to control the bind name precisely; do not set --ldap_domain or --ldap_baseDN with"
129 bool SaslAuthProvider::env_setup_complete_ =
false;
142 if (message == NULL)
return SASL_BADPARAM;
143 const char* authctx = (context == NULL) ?
"Unknown" :
144 reinterpret_cast<const char*>(context);
152 LOG(ERROR) <<
"SASL message (" << authctx <<
"): " << message;
155 LOG(WARNING) <<
"SASL message (" << authctx <<
"): " << message;
158 LOG(INFO) <<
"SASL message (" << authctx <<
"): " << message;
161 VLOG(1) <<
"SASL message (" << authctx <<
"): " << message;
165 VLOG(3) <<
"SASL message (" << authctx <<
"): " << message;
191 const char* pass,
unsigned passlen,
struct propctx* propctx) {
192 if (passlen == 0 && !FLAGS_ldap_allow_anonymous_binds) {
198 int rc = ldap_initialize(&ld, FLAGS_ldap_uri.c_str());
199 if (rc != LDAP_SUCCESS) {
200 LOG(WARNING) <<
"Could not initialize connection with LDAP server ("
201 << FLAGS_ldap_uri <<
"). Error: " << ldap_err2string(rc);
207 ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldap_ver);
213 int tls_rc = ldap_start_tls_s(ld, NULL, NULL);
214 if (tls_rc != LDAP_SUCCESS) {
215 LOG(WARNING) <<
"Could not start TLS secure connection to LDAP server ("
216 << FLAGS_ldap_uri <<
"). Error: " << ldap_err2string(tls_rc);
217 ldap_unbind_ext(ld, NULL, NULL);
220 VLOG(2) <<
"Started TLS connection with LDAP server: " << FLAGS_ldap_uri;
224 string user_str = user;
225 if (!FLAGS_ldap_domain.empty()) {
227 if (user_str.find(
"@") == string::npos) {
228 user_str = Substitute(
"$0@$1", user_str, FLAGS_ldap_domain);
230 }
else if (!FLAGS_ldap_baseDN.empty()) {
231 user_str = Substitute(
"uid=$0,$1", user_str, FLAGS_ldap_baseDN);
232 }
else if (!FLAGS_ldap_bind_pattern.empty()) {
233 user_str = FLAGS_ldap_bind_pattern;
234 replace_all(user_str,
"#UID", user);
239 cred.bv_val =
const_cast<char*
>(pass);
240 cred.bv_len = passlen;
242 VLOG_QUERY <<
"Trying simple LDAP bind for: " << user_str;
244 rc = ldap_sasl_bind_s(ld, user_str.c_str(), LDAP_SASL_SIMPLE, &cred,
247 ldap_unbind_ext(ld, NULL, NULL);
248 if (rc != LDAP_SUCCESS) {
249 LOG(WARNING) <<
"LDAP authentication failure for " << user_str
250 <<
" : " << ldap_err2string(rc);
272 static int SaslGetOption(
void* context,
const char* plugin_name,
const char* option,
273 const char** result,
unsigned* len) {
275 if (plugin_name == NULL) {
276 if (strcmp(
"log_level", option) == 0) {
277 int level = SASL_LOG_WARN;
279 level = SASL_LOG_DEBUG;
281 level = SASL_LOG_TRACE;
284 snprintf(buf, 4,
"%d", level);
286 if (len != NULL) *len = strlen(buf);
288 }
else if (strcmp(
"auxprop_plugin", option) == 0) {
290 if (len != NULL) *len = strlen(*result);
294 VLOG(3) <<
"Sasl general option " << option <<
" requested";
298 VLOG(3) <<
"Sasl option " << plugin_name <<
" : "
299 << option <<
" requested";
307 unsigned int flags,
const char* user,
unsigned ulen) {
311 if ((flags & SASL_AUXPROP_AUTHZID) == 0) {
312 string ustr(user, ulen);
313 VLOG(2) <<
"Attempting to authenticate user \"" << ustr <<
"\"";
332 sasl_auxprop_plug_t** plug,
const char* plugname) {
333 VLOG(2) <<
"Initializing Impala SASL plugin: " << plugname;
335 *out_version = max_version;
348 sasl_verify_type_t type ) {
350 case SASL_VRFY_PLUGIN:
351 VLOG(2) <<
"Sasl found plugin " << file;
354 VLOG(2) <<
"Sasl trying to access config file " << file;
356 case SASL_VRFY_PASSWD:
357 VLOG(2) <<
"Sasl accessing password file " << file;
360 VLOG(2) <<
"Sasl found other file " << file;
381 const char* requested_user,
unsigned rlen,
382 const char* auth_identity,
unsigned alen,
383 const char* def_realm,
unsigned urlen,
384 struct propctx* propctx) {
387 VLOG(1) <<
"Successfully authenticated principal \"" << string(requested_user, rlen)
388 <<
"\" on an internal connection";
407 const char* requested_user,
unsigned rlen,
408 const char* auth_identity,
unsigned alen,
409 const char* def_realm,
unsigned urlen,
410 struct propctx* propctx) {
411 LOG(INFO) <<
"Successfully authenticated client user \""
412 << string(requested_user, rlen) <<
"\"";
429 *path = FLAGS_sasl_path.c_str();
442 static const int MIN_TICKET_LIFETIME_IN_MINS = 1440;
448 int ticket_lifetime_mins =
449 max(MIN_TICKET_LIFETIME_IN_MINS, FLAGS_kerberos_reinit_interval * 2);
454 const string kinit_cmd = Substitute(
"kinit -r $0m -k -t $1 $2 2>&1",
455 ticket_lifetime_mins, keytab_file_, principal_);
457 bool first_time =
true;
458 int failures_since_renewal = 0;
460 LOG(INFO) <<
"Registering " << principal_ <<
", keytab file " << keytab_file_;
465 const string& err_msg = Substitute(
466 "Failed to obtain Kerberos ticket for principal: $0. $1", principal_,
472 LOG(ERROR) << err_msg;
481 failures_since_renewal = 0;
486 string krenew_output;
488 LOG(INFO) <<
"Successfully renewed Keberos ticket";
492 ++failures_since_renewal;
493 LOG(ERROR) <<
"Failed to extend Kerberos ticket. Error: " << krenew_output
494 <<
". Failure count: " << failures_since_renewal;
501 uniform_int<> dist(0, 300);
502 SleepForMs(1000 * max((60 * FLAGS_kerberos_reinit_interval) - dist(generator), 60));
510 if (FLAGS_enable_ldap_auth || !FLAGS_principal.empty()) {
533 if (!FLAGS_principal.empty()) {
561 if (FLAGS_enable_ldap_auth) {
587 stringstream err_msg;
588 err_msg <<
"Could not initialize Sasl library: " << e.what();
589 return Status(err_msg.str());
595 return Status(Substitute(
"Error adding Sasl auxprop plugin: $0",
596 sasl_errstring(rc, NULL, NULL)));
611 if (stat(
"/var/tmp", &st) < 0) {
615 if (!(st.st_mode & S_IFDIR)) {
616 return Status(
"Error: /var/tmp is not a directory");
619 if ((st.st_mode & 01777) != 01777) {
620 return Status(
"Error: The permissions on /var/tmp must precisely match "
621 "\"drwxrwxrwt\". This directory is used by the Kerberos replay cache. To "
622 "rectify this issue, run \"chmod 01777 /var/tmp\" as root.");
628 Status SaslAuthProvider::InitKerberos(
const string& principal,
629 const string& keytab_file) {
630 principal_ = principal;
631 keytab_file_ = keytab_file;
634 needs_kinit_ = is_internal_;
638 if (off != string::npos) {
644 vector<string> names;
645 split(names, principal_, is_any_of(
"/@"));
647 if (names.size() != 3) {
648 return Status(Substitute(
"Kerberos principal should be of the form: "
649 "<service>/<hostname>@<realm> - got: $0", principal_));
652 service_name_ = names[0];
653 hostname_ = names[1];
659 LOG(INFO) <<
"Using " << (is_internal_ ?
"internal" :
"external")
660 <<
" kerberos principal \"" << service_name_ <<
"/"
661 << hostname_ <<
"@" << realm_ <<
"\"";
668 static Status EnvAppend(
const string& attr,
const string& thing,
const string& thingval) {
674 char* current_val_c = getenv(attr.c_str());
675 if (current_val_c != NULL) {
676 current_val = current_val_c;
679 if (!current_val.empty() && (current_val.find(thing) != string::npos)) {
684 stringstream val_out;
685 if (!current_val.empty()) {
687 val_out << current_val <<
" ";
689 val_out <<
"-D" << thing <<
"=" << thingval;
691 if (setenv(attr.c_str(), val_out.str().c_str(), 1) < 0) {
692 return Status(Substitute(
"Bad $0=$1 value: Could not set environment variable $2: $3",
699 Status SaslAuthProvider::InitKerberosEnv() {
700 DCHECK(!principal_.empty());
705 if (!is_regular(keytab_file_)) {
706 return Status(Substitute(
"Bad --keytab_file value: The file $0 is not a "
707 "regular file", keytab_file_));
712 if (setenv(
"KRB5_KTNAME", keytab_file_.c_str(), 1)) {
713 return Status(Substitute(
"Kerberos could not set KRB5_KTNAME: $0",
722 (void) setenv(
"KRB5CCNAME",
"/tmp/krb5cc_impala_internal", 1);
726 if (!FLAGS_krb5_conf.empty()) {
728 if (!is_regular(FLAGS_krb5_conf)) {
729 return Status(Substitute(
"Bad --krb5_conf value: The file $0 is not a "
730 "regular file", FLAGS_krb5_conf));
734 if (setenv(
"KRB5_CONFIG", FLAGS_krb5_conf.c_str(), 1) < 0) {
735 return Status(Substitute(
"Bad --krb5_conf value: Could not set "
742 LOG(INFO) <<
"Using custom Kerberos configuration file at "
747 if (!FLAGS_krb5_debug_file.empty()) {
748 bool krb5_debug_fail =
false;
749 if (setenv(
"KRB5_TRACE", FLAGS_krb5_debug_file.c_str(), 1) < 0) {
750 LOG(WARNING) <<
"Failed to set KRB5_TRACE; --krb5_debuf_file not enabled for "
752 krb5_debug_fail =
true;
754 if (!
EnvAppend(
"JAVA_TOOL_OPTIONS",
"sun.security.krb5.debug",
"true").ok()) {
755 LOG(WARNING) <<
"Failed to set JAVA_TOOL_OPTIONS; --krb5_debuf_file not enabled "
756 "for front-end code";
757 krb5_debug_fail =
true;
759 if (!krb5_debug_fail) {
760 LOG(INFO) <<
"Kerberos debugging is enabled; kerberos messages written to "
761 << FLAGS_krb5_debug_file;
765 env_setup_complete_ =
true;
772 DCHECK(is_internal_);
773 DCHECK(!principal_.empty());
775 stringstream thread_name;
776 thread_name <<
"kinit-" << principal_;
777 kinit_thread_.reset(
new Thread(
"authentication", thread_name.str(),
778 &SaslAuthProvider::RunKinit,
this, &first_kinit));
779 LOG(INFO) <<
"Waiting for Kerberos ticket for principal: " << principal_;
781 LOG(INFO) <<
"Kerberos ticket granted to " << principal_;
785 DCHECK(!is_internal_);
786 if (!FLAGS_ldap_ca_certificate.empty()) {
787 int set_rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE,
788 FLAGS_ldap_ca_certificate.c_str());
789 if (set_rc != LDAP_SUCCESS) {
790 return Status(Substitute(
"Could not set location of LDAP server cert: $0",
791 ldap_err2string(set_rc)));
795 int val = LDAP_OPT_X_TLS_ALLOW;
796 int set_rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT,
797 reinterpret_cast<void*>(&val));
798 if (set_rc != LDAP_SUCCESS) {
800 "Could not disable certificate requirement for LDAP server: $0",
801 ldap_err2string(set_rc)));
805 if (hostname_.empty()) {
813 Status SaslAuthProvider::GetServerTransportFactory(
814 shared_ptr<TTransportFactory>* factory) {
815 DCHECK(!principal_.empty() || has_ldap_);
820 map<string, string> sasl_props;
824 if(!principal_.empty()) {
827 hostname_, realm_, 0, sasl_props,
837 }
catch (
const TException& e) {
838 LOG(ERROR) <<
"Failed to create Sasl Server transport factory: "
843 VLOG_RPC <<
"Made " << (is_internal_ ?
"internal" :
"external")
844 <<
" server transport factory with "
845 << (!principal_.empty() ?
"Kerberos " :
" ")
846 << (has_ldap_ ?
"LDAP " :
" ") <<
"authentication";
851 Status SaslAuthProvider::WrapClientTransport(
const string& hostname,
852 shared_ptr<TTransport> raw_transport,
const string& service_name,
853 shared_ptr<TTransport>* wrapped_transport) {
855 shared_ptr<sasl::TSasl> sasl_client;
856 const map<string, string> props;
857 const string auth_id;
860 DCHECK(is_internal_);
864 const string& service = service_name.empty() ? service_name_ : service_name;
868 LOG(ERROR) <<
"Failed to create a GSSAPI/SASL client: " << e.what();
877 VLOG_RPC <<
"Initiating client connection using principal " << principal_;
882 Status NoAuthProvider::GetServerTransportFactory(shared_ptr<TTransportFactory>* factory) {
884 factory->reset(
new TBufferedTransportFactory());
888 Status NoAuthProvider::WrapClientTransport(
const string& hostname,
889 shared_ptr<TTransport> raw_transport,
const string& dummy_service,
890 shared_ptr<TTransport>* wrapped_transport) {
892 *wrapped_transport = raw_transport;
897 bool use_ldap =
false;
898 const string excl_msg =
"--$0 and --$1 are mutually exclusive "
899 "and should not be set together";
902 if (FLAGS_enable_ldap_auth) {
905 if (!FLAGS_ldap_domain.empty()) {
906 if (!FLAGS_ldap_baseDN.empty()) {
907 return Status(Substitute(excl_msg,
"ldap_domain",
"ldap_baseDN"));
909 if (!FLAGS_ldap_bind_pattern.empty()) {
910 return Status(Substitute(excl_msg,
"ldap_domain",
"ldap_bind_pattern"));
912 }
else if (!FLAGS_ldap_baseDN.empty()) {
913 if (!FLAGS_ldap_bind_pattern.empty()) {
914 return Status(Substitute(excl_msg,
"ldap_baseDN",
"ldap_bind_pattern"));
918 if (FLAGS_ldap_uri.empty()) {
919 return Status(
"--ldap_uri must be supplied when --ldap_enable_auth is set");
924 return Status(Substitute(
"--ldap_uri must start with either $0 or $1",
928 LOG(INFO) <<
"Using LDAP authentication with server " << FLAGS_ldap_uri;
931 if (FLAGS_ldap_passwords_in_clear_ok) {
932 LOG(WARNING) <<
"LDAP authentication is being used, but without TLS. "
933 <<
"ALL PASSWORDS WILL GO OVER THE NETWORK IN THE CLEAR.";
935 return Status(
"LDAP authentication specified, but without TLS. "
936 "Passwords would go over the network in the clear. "
937 "Enable TLS with --ldap_tls or use an ldaps:// URI. "
938 "To override this is non-production environments, "
939 "specify --ldap_passwords_in_clear_ok");
941 }
else if (FLAGS_ldap_ca_certificate.empty()) {
942 LOG(WARNING) <<
"LDAP authentication is being used with TLS, but without "
943 <<
"an --ldap_ca_certificate file, the identity of the LDAP "
944 <<
"server cannot be verified. Network communication (and "
945 <<
"hence passwords) could be intercepted by a "
946 <<
"man-in-the-middle attack";
950 if (FLAGS_principal.empty() && !FLAGS_be_principal.empty()) {
951 return Status(
"A back end principal (--be_principal) was supplied without "
952 "also supplying a regular principal (--principal). Either --principal "
953 "must be supplied alone, in which case it applies to all communication, "
954 "or --principal and --be_principal must be supplied together, in which "
955 "case --principal is used in external communication and --be_principal "
956 "is used in internal (back-end) communication.");
960 string kerberos_external_principal;
962 string kerberos_internal_principal;
964 if (!FLAGS_principal.empty()) {
965 kerberos_external_principal = FLAGS_principal;
966 if (FLAGS_be_principal.empty()) {
967 kerberos_internal_principal = FLAGS_principal;
969 kerberos_internal_principal = FLAGS_be_principal;
989 if (!kerberos_internal_principal.empty()) {
994 LOG(INFO) <<
"Internal communication is authenticated with Kerberos";
997 LOG(INFO) <<
"Internal communication is not authenticated";
1004 if (use_ldap || !kerberos_external_principal.empty()) {
1007 if (!kerberos_external_principal.empty()) {
1009 FLAGS_keytab_file));
1010 LOG(INFO) <<
"External communication is authenticated with Kerberos";
1014 LOG(INFO) <<
"External communication is authenticated with LDAP";
1018 LOG(INFO) <<
"External communication is not authenticated";
1026 DCHECK(external_auth_provider_.get() != NULL);
1027 return external_auth_provider_.get();
1031 DCHECK(internal_auth_provider_.get() != NULL);
1032 return internal_auth_provider_.get();
#define VLOG_CONNECTION_IS_ON
Status InitAuth(const string &appname)
string path("/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:/usr/lib/x86_64-linux-gnu/sasl2")
TODO: Consider allowing fragment IDs as category parameters.
static vector< sasl_callback_t > KERB_INT_CALLBACKS
DEFINE_string(sasl_path,"/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:""/usr/lib/x86_64-linux-gnu/sasl2","Colon separated list of paths to look for SASL ""security library plugins.")
static sasl_callback_t GENERAL_CALLBACKS[5]
static const string PLAIN_MECHANISM
bool RunShellProcess(const std::string &cmd, std::string *msg)
#define RETURN_IF_ERROR(stmt)
some generally useful macros
DECLARE_string(keytab_file)
Status InitKerberos(const std::string &principal, const std::string &keytab_path)
static const string HOSTNAME_PATTERN
static void SaslInit(const sasl_callback_t *callbacks, const std::string &appname)
static vector< sasl_callback_t > KERB_EXT_CALLBACKS
Status CheckReplayCacheDirPermissions()
static void ImpalaAuxpropLookup(void *glob_context, sasl_server_params_t *sparams, unsigned int flags, const char *user, unsigned ulen)
void SleepForMs(const int64_t duration_ms)
Sleeps the current thread for at least duration_ms milliseconds.
static const string LDAPS_URI_PREFIX
static int SaslAuthorizeExternal(sasl_conn_t *conn, void *context, const char *requested_user, unsigned rlen, const char *auth_identity, unsigned alen, const char *def_realm, unsigned urlen, struct propctx *propctx)
static const string IMPALA_AUXPROP_PLUGIN
static sasl_auxprop_plug_t impala_auxprop_plugin
static int SaslVerifyFile(void *context, const char *file, sasl_verify_type_t type)
static const string KERBEROS_MECHANISM
DEFINE_int32(kerberos_reinit_interval, 60,"Interval, in minutes, between kerberos ticket renewals. Each renewal will request ""a ticket with a lifetime that is at least 2x the renewal interval.")
void addServerDefinition(const std::string &mechanism, const std::string &protocol, const std::string &serverName, const std::string &realm, unsigned int flags, std::map< std::string, std::string > props, std::vector< struct sasl_callback > callbacks)
static int SaslLogCallback(void *context, int level, const char *message)
Status GetHostname(string *hostname)
DEFINE_bool(enable_ldap_auth, false,"If true, use LDAP authentication for client connections")
static Status EnvAppend(const string &attr, const string &thing, const string &thingval)
int SaslLdapCheckPass(sasl_conn_t *conn, void *context, const char *user, const char *pass, unsigned passlen, struct propctx *propctx)
static const string LDAP_URI_PREFIX
static int SaslGetPath(void *context, const char **path)
static vector< sasl_callback_t > LDAP_EXT_CALLBACKS
int ImpalaAuxpropInit(const sasl_utils_t *utils, int max_version, int *out_version, sasl_auxprop_plug_t **plug, const char *plugname)
static int SaslAuthorizeInternal(sasl_conn_t *conn, void *context, const char *requested_user, unsigned rlen, const char *auth_identity, unsigned alen, const char *def_realm, unsigned urlen, struct propctx *propctx)
static void SaslInit(sasl_callback_t *callbacks)
static int SaslGetOption(void *context, const char *plugin_name, const char *option, const char **result, unsigned *len)