blob: 308e8d55c450f7e7bc60771a09a12ecc3927a268 [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.
#include "testutil/gtest-util.h"
#include "testutil/scoped-flag-setter.h"
#include "common/init.h"
#include "common/logging.h"
#include "kudu/rpc/sasl_common.h"
#include "kudu/security/init.h"
#include "kudu/security/test/mini_kdc.h"
#include "rpc/authentication.h"
#include "rpc/authentication-util.h"
#include "rpc/thrift-server.h"
#include "util/auth-util.h"
#include "util/kudu-status-util.h"
#include "util/network-util.h"
#include "util/openssl-util.h"
#include <ldap.h>
DECLARE_bool(enable_ldap_auth);
DECLARE_string(ldap_uri);
DECLARE_string(keytab_file);
DECLARE_string(principal);
DECLARE_string(ssl_client_ca_certificate);
DECLARE_string(ssl_server_certificate);
DECLARE_string(ssl_private_key);
DECLARE_string(internal_principals_whitelist);
DECLARE_bool(allow_custom_ldap_filters_with_kerberos_auth);
DECLARE_bool(ldap_search_bind_authentication);
DECLARE_string(ldap_user_search_basedn);
DECLARE_string(ldap_user_filter);
DECLARE_string(ldap_group_filter);
// These are here so that we can grab them early in main() - the kerberos
// init can clobber KRB5_KTNAME in PrincipalSubstitution.
static const char *env_keytab = NULL;
static const char *env_princ = NULL;
#include "common/names.h"
namespace impala {
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);
TEST(Auth, PrincipalSubstitution) {
string hostname;
ASSERT_OK(GetHostname(&hostname));
SecureAuthProvider sa(false); // false means it's external
FLAGS_principal = "service_name/_HOST@some.realm";
string principal;
ASSERT_OK(GetExternalKerberosPrincipal(&principal));
ASSERT_OK(sa.InitKerberos(principal));
ASSERT_OK(sa.Start());
ASSERT_EQ(string::npos, sa.principal().find("_HOST"));
ASSERT_NE(string::npos, sa.principal().find(hostname));
ASSERT_EQ("service_name", sa.service_name());
ASSERT_EQ(hostname, sa.hostname());
ASSERT_EQ("some.realm", sa.realm());
FLAGS_principal.clear();
}
void AuthOk(const string& name, SecureAuthProvider* sa) {
EXPECT_EQ(SASL_OK,
SaslAuthorizeInternal(NULL, (void*)sa, name.c_str(), name.size(), NULL, 0, NULL, 0,
NULL));
}
void AuthFails(const string& name, SecureAuthProvider* sa) {
EXPECT_EQ(SASL_BADAUTH,
SaslAuthorizeInternal(NULL, (void*)sa, name.c_str(), name.size(), NULL, 0, NULL, 0,
NULL));
}
TEST(Auth, AuthorizeInternalPrincipals) {
SecureAuthProvider sa(true); // false means it's external
ASSERT_OK(sa.InitKerberos("service_name/localhost@some.realm"));
AuthOk("service_name/localhost@some.realm", &sa);
AuthFails("unknown/localhost@some.realm", &sa);
FLAGS_internal_principals_whitelist = "hdfs1,hdfs2";
AuthOk("hdfs1/localhost@some.realm", &sa);
AuthOk("hdfs2/localhost@some.realm", &sa);
AuthFails("hdfs/localhost@some.realm", &sa);
AuthFails("hdfs1@some.realm", &sa);
AuthFails("/localhost@some.realm", &sa);
FLAGS_internal_principals_whitelist = "";
AuthFails("", &sa);
FLAGS_internal_principals_whitelist = ",";
AuthFails("", &sa);
FLAGS_internal_principals_whitelist = " ,";
AuthFails("", &sa);
}
TEST(Auth, ValidAuthProviders) {
ASSERT_OK(AuthManager::GetInstance()->Init());
ASSERT_TRUE(AuthManager::GetInstance()->GetExternalAuthProvider() != NULL);
ASSERT_TRUE(AuthManager::GetInstance()->GetExternalHttpAuthProvider() != NULL);
ASSERT_TRUE(AuthManager::GetInstance()->GetInternalAuthProvider() != NULL);
}
// Set up ldap flags and ensure we make the appropriate auth providers
TEST(Auth, LdapAuth) {
AuthProvider* ap = NULL;
SecureAuthProvider* sa = NULL;
FLAGS_enable_ldap_auth = true;
FLAGS_ldap_uri = "ldaps://bogus.com";
// Initialization based on above "command line" args
ASSERT_OK(AuthManager::GetInstance()->Init());
// External auth provider is sasl, ldap, but not kerberos
ap = AuthManager::GetInstance()->GetExternalAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_TRUE(sa->has_ldap());
ASSERT_EQ("", sa->principal());
ap = AuthManager::GetInstance()->GetExternalHttpAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_TRUE(sa->has_ldap());
ASSERT_EQ("", sa->principal());
// Internal auth provider isn't sasl.
ap = AuthManager::GetInstance()->GetInternalAuthProvider();
ASSERT_FALSE(ap->is_secure());
}
// Set up ldap and kerberos flags and ensure we make the appropriate auth providers
TEST(Auth, LdapKerbAuth) {
AuthProvider* ap = NULL;
SecureAuthProvider* sa = NULL;
if ((env_keytab == NULL) || (env_princ == NULL)) {
return; // In a non-kerberized environment
}
FLAGS_keytab_file = env_keytab;
FLAGS_principal = env_princ;
FLAGS_enable_ldap_auth = true;
FLAGS_ldap_uri = "ldaps://bogus.com";
// Initialization based on above "command line" args
ASSERT_OK(AuthManager::GetInstance()->Init());
// External auth provider is sasl, ldap, and kerberos
ap = AuthManager::GetInstance()->GetExternalAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_TRUE(sa->has_ldap());
ASSERT_EQ(FLAGS_principal, sa->principal());
ap = AuthManager::GetInstance()->GetExternalHttpAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_TRUE(sa->has_ldap());
ASSERT_EQ(FLAGS_principal, sa->principal());
// Internal auth provider is sasl and kerberos
ap = AuthManager::GetInstance()->GetInternalAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_FALSE(sa->has_ldap());
ASSERT_EQ(FLAGS_principal, sa->principal());
}
// Test for IMPALA-2598: SSL and Kerberos do not mix on server<->server connections.
// Tests that Impala will successfully start if so configured.
TEST(Auth, KerbAndSslEnabled) {
string hostname;
ASSERT_OK(GetHostname(&hostname));
FLAGS_ssl_client_ca_certificate = "some_path";
FLAGS_ssl_server_certificate = "some_path";
FLAGS_ssl_private_key = "some_path";
ASSERT_TRUE(IsInternalTlsConfigured());
SecureAuthProvider sa_internal(true);
ASSERT_OK(sa_internal.InitKerberos("service_name/_HOST@some.realm"));
SecureAuthProvider sa_external(false);
ASSERT_OK(sa_external.InitKerberos("service_name/_HOST@some.realm"));
}
// Test principal with slash in hostname
TEST(Auth, InternalPrincipalWithSlash) {
SecureAuthProvider sa(false); // false means it's external
ASSERT_OK(sa.InitKerberos("service_name/local\\/host@some.realm"));
ASSERT_OK(sa.Start());
ASSERT_EQ("service_name", sa.service_name());
ASSERT_EQ("local/host", sa.hostname());
ASSERT_EQ("some.realm", sa.realm());
}
// Test bad principal format exception
TEST(Auth, BadPrincipalFormat) {
SecureAuthProvider sa(false); // false means it's external
EXPECT_ERROR(sa.InitKerberos(""), 2);
EXPECT_ERROR(sa.InitKerberos("service_name@some.realm"), 2);
EXPECT_ERROR(sa.InitKerberos("service_name/localhost"), 2);
}
// Set up ldap and kerberos flags and
// 1. check if there is an error when specifying custom search filters,
// 2. and the error does not occur when the
// allow_custom_ldap_filters_with_kerberos_auth flag is turned on.
TEST(Auth, LdapKerbAuthCustomFiltersNotAllowed) {
AuthProvider* ap = NULL;
SecureAuthProvider* sa = NULL;
// Initialize the mini kdc.
kudu::MiniKdc kdc(kudu::MiniKdcOptions{});
KUDU_ASSERT_OK(kdc.Start());
kdc.SetKrb5Environment();
string kt_path;
KUDU_ASSERT_OK(kdc.CreateServiceKeytab("HTTP/127.0.0.1", &kt_path));
CHECK_ERR(setenv("KRB5_KTNAME", kt_path.c_str(), 1));
KUDU_ASSERT_OK(kdc.CreateUserPrincipal("alice"));
KUDU_ASSERT_OK(kdc.Kinit("alice"));
// Set up a fake impala server with Kerberos enabled.
gflags::FlagSaver saver;
FLAGS_principal = "HTTP/127.0.0.1@KRBTEST.COM";
FLAGS_keytab_file = kt_path;
FLAGS_enable_ldap_auth = true;
FLAGS_ldap_uri = "ldaps://bogus.com";
FLAGS_skip_external_kerberos_auth = false; // external Kerberos auth enabled
FLAGS_skip_internal_kerberos_auth = true; // internal Kerberos auth disabled (no auth)
FLAGS_ldap_user_search_basedn = "dc=kbrtest,dc=com";
FLAGS_ldap_search_bind_authentication = true;
FLAGS_ldap_user_filter = "(&(objectClass=user)(sAMAccountName={0}))";
// Initialization based on above "command line" args should fail
// due to the custom ldap search filters specified.
ASSERT_ERROR_MSG(AuthManager::GetInstance()->Init(),
"LDAP user and group filters may not be used "
"if Kerberos auth is turned on for external connections.");
// Initialization based on above "command line" args should not fail
// if the use of custom ldap filters is enabled with Kerberos auth.
FLAGS_allow_custom_ldap_filters_with_kerberos_auth = true;
ASSERT_OK(AuthManager::GetInstance()->Init());
// External auth provider is sasl, ldap, and kerberos
ap = AuthManager::GetInstance()->GetExternalAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_TRUE(sa->has_ldap());
ASSERT_EQ(FLAGS_principal, sa->principal());
ap = AuthManager::GetInstance()->GetExternalHttpAuthProvider();
ASSERT_TRUE(ap->is_secure());
sa = dynamic_cast<SecureAuthProvider*>(ap);
ASSERT_TRUE(sa->has_ldap());
ASSERT_EQ(FLAGS_principal, sa->principal());
// Internal auth provider is not secure (NoAuthProvider)
ap = AuthManager::GetInstance()->GetInternalAuthProvider();
ASSERT_FALSE(ap->is_secure());
}
TEST(Auth, GetXFFOriginClientAddress) {
// Empty XFF address
std::string xff_addresses;
std::string origin;
Status status = GetXFFOriginClientAddress(xff_addresses, origin);
ASSERT_EQ(status.msg().msg(), "Empty XFF header");
// Empty ',' separated XFF address
xff_addresses = " ,";
status = GetXFFOriginClientAddress(xff_addresses, origin);
ASSERT_EQ(status.msg().msg(), "Empty XFF header");
// Multiple XFF addresses
xff_addresses = " 10.96.11.12, 172.45.88.78, 176.45.88.65";
status = GetXFFOriginClientAddress(xff_addresses, origin);
ASSERT_EQ(origin, "10.96.11.12");
ASSERT_TRUE(status.ok());
// Single XFF address
xff_addresses = "10.95.66.23";
status = GetXFFOriginClientAddress(xff_addresses, origin);
ASSERT_EQ(origin, "10.95.66.23");
ASSERT_TRUE(status.ok());
xff_addresses = " 10.90.11.23, ";
status = GetXFFOriginClientAddress(xff_addresses, origin);
ASSERT_EQ(origin, "10.90.11.23");
ASSERT_TRUE(status.ok());
xff_addresses = " 10.90.11.23 , ";
status = GetXFFOriginClientAddress(xff_addresses, origin);
ASSERT_EQ(origin, "10.90.11.23");
ASSERT_TRUE(status.ok());
}
// Checks that GetEffectiveUser() returns the expected values.
void assertEffectiveUser(
const string& connected_user, const string& delegated_user, const string& expected) {
TSessionState session;
if (!connected_user.empty()) session.__set_connected_user(connected_user);
if (!delegated_user.empty()) session.__set_delegated_user(delegated_user);
ASSERT_EQ(GetEffectiveUser(session), expected);
}
// Checks that GetEffectiveShortUser() returns the expected values.
void assertEffectiveShortUser(
const string& connected_user, const string& delegated_user, const string& expected) {
TSessionState session;
if (!connected_user.empty()) session.__set_connected_user(connected_user);
if (!delegated_user.empty()) session.__set_delegated_user(delegated_user);
string returned_user;
ASSERT_OK(GetEffectiveShortUser(session, &returned_user));
ASSERT_EQ(returned_user, expected)
<< "connected=" << connected_user << " delegated=" << delegated_user;
}
// Unit test for GetEffectiveShortUser().
TEST(Auth, UserUtilities) {
// Enable Kerberos so we can test kerberos names in GetEffectiveShortUser().
auto enable_kerberos =
ScopedFlagSetter<string>::Make(&FLAGS_principal, "service_name/_HOST@some.realm");
// Usernames that are not mutated by GetEffectiveShortUser().
const char* unchanged_usernames[] = {
"andrew", "andrew_sherman", "andrew-sherman", "Andrew"};
for (auto& name : unchanged_usernames) {
assertEffectiveUser(name, "", name);
assertEffectiveShortUser(name, "", name);
}
// Test GetEffectiveShortUser() maps Kerberos usernames to the expected short name.
std::pair<const char*, const char*> kerberos_name_mappings[] = {
{"impala@ROOT.COMOPS.SITE", "impala"},
{"changepw/kdc1.example.com@example.com", "changepw"},
{"krbtgt/EAST.EXAMPLE.COM@WEST.EXAMPLE.COM", "krbtgt"},
{"User1/admin/STAFF/employees@WEST.EXAMPLE.COM", "User1"},
{"employees@", "employees"}
};
for (const auto& pair : kerberos_name_mappings) {
assertEffectiveShortUser(pair.first, "", pair.second);
}
// Test GetEffectiveUser() logic.
assertEffectiveUser("connected1", "delegated1", "delegated1");
assertEffectiveUser("connected1", "", "connected1");
assertEffectiveUser("impala@ROOT.COMOPS.SITE", "", "impala@ROOT.COMOPS.SITE");
// Illegal kerberos user names.
// These all fail kudu::security::MapPrincipalToLocalName().
const char* illegal_kerberos_usernames[] = {
"buggy@EAST.EXAMPLE.COM/WEST.EXAMPLE.COM",
"two_ats@kdc1.example.com@EXAMPLE.COM",
"two_slash/kdc1.example.com@/EXAMPLE.COM",
"/User1/admin/STAFF/employees@WEST.EXAMPLE.COM",
};
for (auto& bad_name : illegal_kerberos_usernames) {
TSessionState session;
session.__set_connected_user(bad_name);
string unused;
stringstream ss;
ss << "Could not parse Kerberos name " << bad_name;
ASSERT_ERROR_MSG(GetEffectiveShortUser(session, &unused), ss.str());
}
}
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
impala::InitCommonRuntime(argc, argv, true, impala::TestInfo::BE_TEST);
env_keytab = getenv("KRB5_KTNAME");
env_princ = getenv("MINIKDC_PRINC_IMPALA");
return RUN_ALL_TESTS();
}