kudu-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From t...@apache.org
Subject [3/3] kudu git commit: Workaround test failures running with MIT krb5 1.10
Date Thu, 10 Nov 2016 18:22:55 GMT
Workaround test failures running with MIT krb5 1.10

This fixes a variety of issues seen when running on el6, which has MIT
krb5 1.10.

* This version doesn't support KRB5_CLIENT_KTNAME. So, we need to
  explicitly login servers from their provided keytabs when the server
  starts. This ensures that servers have proper client credentials in
  order to send kerberized RPCs to other servers. Along the way,
  I changed the ExternalMiniCluster to provide IP-specific keytabs to
  the server.

* In our minicluster, we use strange local IPs like '127.x.y.z' for
  testing. These don't have any associated hostnames in reverse DNS. MIT
  krb5 has a bug (or lack of feature) whereby it cannot handle service
  principals whose hostname component is a numeric IP rather than a
  hostname. Specifically, it fails to determine the Kerberos Realm for a
  principal with a numeric host.

  This patch works around the issue by overriding the
  krb5_get_host_realm() function using a combination of LD_PRELOAD and
  statically including this symbol in our tests. See the comments at the
  top of krb5_realm_override.cc for details.

With this patch, external_mini_cluster-test passes on el6 whereas it
used to fail.

Change-Id: I708334cbbee35d2629a38a369e63c1dc309ed91b
Reviewed-on: http://gerrit.cloudera.org:8080/4990
Tested-by: Kudu Jenkins
Reviewed-by: Adar Dembo <adar@cloudera.com>
Reviewed-by: Alexey Serbin <aserbin@cloudera.com>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/ba2ae3de
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/ba2ae3de
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/ba2ae3de

Branch: refs/heads/master
Commit: ba2ae3de4a7c43ff2f5873e822410e066ea99667
Parents: b1b6c88
Author: Todd Lipcon <todd@apache.org>
Authored: Mon Nov 7 21:31:57 2016 -0800
Committer: Todd Lipcon <todd@apache.org>
Committed: Thu Nov 10 17:06:33 2016 +0000

----------------------------------------------------------------------
 CMakeLists.txt                                  |  10 +-
 cmake_modules/FindKerberos.cmake                |  30 ++--
 cmake_modules/FindKerberosPrograms.cmake        |  38 +++++
 src/kudu/integration-tests/CMakeLists.txt       |   4 +-
 .../external_mini_cluster-test.cc               |   5 +-
 .../integration-tests/external_mini_cluster.cc  |  28 ++--
 .../integration-tests/external_mini_cluster.h   |   5 +-
 src/kudu/security/CMakeLists.txt                |  14 ++
 src/kudu/security/init.cc                       | 151 +++++++++++++++++++
 src/kudu/security/init.h                        |  29 ++++
 src/kudu/security/test/krb5_realm_override.cc   | 106 +++++++++++++
 src/kudu/security/test/mini_kdc-test.cc         |  20 ++-
 src/kudu/server/CMakeLists.txt                  |   3 +-
 src/kudu/server/server_base.cc                  |   3 +
 src/kudu/util/CMakeLists.txt                    |   4 +-
 src/kudu/util/test_main.cc                      |   7 +
 16 files changed, 416 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9a76424..b10e3b7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -858,6 +858,12 @@ ADD_THIRDPARTY_LIB(openssl_ssl
 ADD_THIRDPARTY_LIB(openssl_crypto
   SHARED_LIB "${OPENSSL_CRYPTO_LIBRARY}")
 
+## Kerberos
+find_package(Kerberos REQUIRED)
+include_directories(${KERBEROS_INCLUDE_DIR})
+ADD_THIRDPARTY_LIB(krb5
+  SHARED_LIB "${KERBEROS_LIBRARY}")
+
 ## Google PerfTools
 ##
 ## Disabled with TSAN/ASAN as well as with gold+dynamic linking (see comment
@@ -930,10 +936,10 @@ if (NOT APPLE)
     SHARED_LIB "${DL_LIB_PATH}")
 endif()
 
-## Kerberos
+## Kerberos binaries (kinit, kadmin, etc).
 if (NOT NO_TESTS)
   ## We rely on the Kerberos binaries for testing security.
-  find_package(Kerberos)
+  find_package(KerberosPrograms)
 endif()
 
 ## Boost

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/cmake_modules/FindKerberos.cmake
----------------------------------------------------------------------
diff --git a/cmake_modules/FindKerberos.cmake b/cmake_modules/FindKerberos.cmake
index 87b1655..197a30c 100644
--- a/cmake_modules/FindKerberos.cmake
+++ b/cmake_modules/FindKerberos.cmake
@@ -15,24 +15,16 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# - Find Kerberos
-# This module ensures that the Kerberos binaries depended on by tests are
-# present on the system.
-
-include(FindPackageHandleStandardArgs)
-set(bins kadmin.local kdb5_util kdestroy kinit klist krb5kdc)
+# Find the native Kerberos includes and library
+#
+#  KERBEROS_INCLUDE_DIR  - Where to find krb5.h, etc.
+#  KERBEROS_LIBRARY      - List of libraries when using krb5.
+#  KERBEROS_FOUND        - True if krb5 found.
 
-foreach(bin ${bins})
-  find_program(${bin} ${bin} PATHS
-               # Linux install location.
-               /usr/sbin
-               # Homebrew install location.
-               /usr/local/opt/krb5/sbin
-               # Macports install location.
-               /opt/local/sbin
-               # SLES
-               /usr/lib/mit/sbin)
-endforeach(bin)
+find_path(KERBEROS_INCLUDE_DIR krb5.h)
+find_library(KERBEROS_LIBRARY NAMES krb5)
 
-find_package_handle_standard_args(Kerberos REQUIRED_VARS ${bins}
-  FAIL_MESSAGE "Kerberos binaries not found: security tests will fail")
+# handle the QUIETLY and REQUIRED arguments and set KERBEROS_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(KERBEROS DEFAULT_MSG KERBEROS_LIBRARY KERBEROS_INCLUDE_DIR)

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/cmake_modules/FindKerberosPrograms.cmake
----------------------------------------------------------------------
diff --git a/cmake_modules/FindKerberosPrograms.cmake b/cmake_modules/FindKerberosPrograms.cmake
new file mode 100644
index 0000000..27b181a
--- /dev/null
+++ b/cmake_modules/FindKerberosPrograms.cmake
@@ -0,0 +1,38 @@
+# 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.
+
+# - Find Kerberos Binaries
+# This module ensures that the Kerberos binaries depended on by tests are
+# present on the system.
+
+include(FindPackageHandleStandardArgs)
+set(bins kadmin.local kdb5_util kdestroy kinit klist krb5kdc)
+
+foreach(bin ${bins})
+  find_program(${bin} ${bin} PATHS
+               # Linux install location.
+               /usr/sbin
+               # Homebrew install location.
+               /usr/local/opt/krb5/sbin
+               # Macports install location.
+               /opt/local/sbin
+               # SLES
+               /usr/lib/mit/sbin)
+endforeach(bin)
+
+find_package_handle_standard_args(Kerberos REQUIRED_VARS ${bins}
+  FAIL_MESSAGE "Kerberos binaries not found: security tests will fail")

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/integration-tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/CMakeLists.txt b/src/kudu/integration-tests/CMakeLists.txt
index 0fed394..6d58814 100644
--- a/src/kudu/integration-tests/CMakeLists.txt
+++ b/src/kudu/integration-tests/CMakeLists.txt
@@ -38,7 +38,9 @@ target_link_libraries(integration-tests
   security-test)
 add_dependencies(integration-tests
   kudu-tserver
-  kudu-master)
+  kudu-master
+  # ExternalMiniCluster uses this shared library as a LD_PRELOAD
+  krb5_realm_override)
 
 # Tests
 set(KUDU_TEST_LINK_LIBS integration-tests ${KUDU_MIN_TEST_LIBS})

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/integration-tests/external_mini_cluster-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/external_mini_cluster-test.cc b/src/kudu/integration-tests/external_mini_cluster-test.cc
index f316754..9bbf40a 100644
--- a/src/kudu/integration-tests/external_mini_cluster-test.cc
+++ b/src/kudu/integration-tests/external_mini_cluster-test.cc
@@ -137,7 +137,10 @@ TEST_P(ExternalMiniClusterTest, TestBasicOperation) {
   if (opts.enable_kerberos) {
     ASSERT_OK(cluster.kdc()->Kdestroy());
     Status s = cluster.SetFlag(ts, "foo", "bar");
-    ASSERT_STR_MATCHES(s.ToString(), "Not authorized.*No Kerberos credentials");
+    // The error differs depending on the version of Kerberos, so we match
+    // either message.
+    ASSERT_STR_MATCHES(s.ToString(), "Not authorized.*"
+                       "(Credentials cache file.*not found|No Kerberos credentials)");
   }
   cluster.Shutdown();
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/integration-tests/external_mini_cluster.cc
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/external_mini_cluster.cc b/src/kudu/integration-tests/external_mini_cluster.cc
index c6d875a..95a5d8a 100644
--- a/src/kudu/integration-tests/external_mini_cluster.cc
+++ b/src/kudu/integration-tests/external_mini_cluster.cc
@@ -247,7 +247,7 @@ Status ExternalMiniCluster::StartSingleMaster() {
       new ExternalMaster(messenger_, exe, GetDataPath("master-0"), flags);
 
   if (opts_.enable_kerberos) {
-    RETURN_NOT_OK_PREPEND(master->EnableKerberos(kdc_.get()),
+    RETURN_NOT_OK_PREPEND(master->EnableKerberos(kdc_.get(), "127.0.0.1"),
                           "could not enable Kerberos");
   }
 
@@ -282,7 +282,7 @@ Status ExternalMiniCluster::StartDistributedMasters() {
                            peer_addrs[i],
                            SubstituteInFlags(flags, i));
     if (opts_.enable_kerberos) {
-      RETURN_NOT_OK_PREPEND(peer->EnableKerberos(kdc_.get()),
+      RETURN_NOT_OK_PREPEND(peer->EnableKerberos(kdc_.get(), "127.0.0.1"),
                             "could not enable Kerberos");
     }
     RETURN_NOT_OK_PREPEND(peer->Start(),
@@ -314,14 +314,14 @@ Status ExternalMiniCluster::AddTabletServer() {
   for (int i = 0; i < num_masters(); i++) {
     master_hostports.push_back(DCHECK_NOTNULL(master(i))->bound_rpc_hostport());
   }
-
+  string bind_host = GetBindIpForTabletServer(idx);
   scoped_refptr<ExternalTabletServer> ts =
     new ExternalTabletServer(messenger_, exe, GetDataPath(Substitute("ts-$0", idx)),
-                             GetBindIpForTabletServer(idx),
+                             bind_host,
                              master_hostports,
                              SubstituteInFlags(opts_.extra_tserver_flags, idx));
   if (opts_.enable_kerberos) {
-    RETURN_NOT_OK_PREPEND(ts->EnableKerberos(kdc_.get()),
+    RETURN_NOT_OK_PREPEND(ts->EnableKerberos(kdc_.get(), bind_host),
                           "could not enable Kerberos");
   }
 
@@ -576,16 +576,20 @@ ExternalDaemon::ExternalDaemon(std::shared_ptr<rpc::Messenger>
messenger,
 ExternalDaemon::~ExternalDaemon() {
 }
 
-Status ExternalDaemon::EnableKerberos(MiniKdc* kdc) {
+Status ExternalDaemon::EnableKerberos(MiniKdc* kdc, const string& bind_host) {
+  string spn = "kudu/" + bind_host;
   string ktpath;
-  RETURN_NOT_OK_PREPEND(kdc->CreateServiceKeytab("kudu/127.0.0.1", &ktpath),
+  RETURN_NOT_OK_PREPEND(kdc->CreateServiceKeytab(spn, &ktpath),
                         "could not create keytab");
   extra_env_ = kdc->GetEnvVars();
-  extra_env_["KRB5_KTNAME"] = ktpath;
-  extra_env_["KRB5_CLIENT_KTNAME"] = ktpath;
-  // Have the daemons use an in-memory ticket cache, so they don't accidentally
-  // pick up credentials from the test case itself or from any other daemon.
-  extra_env_["KRB5CCNAME"] = "MEMORY:foo";
+
+  // Add workaround for krb5 1.10 bug. See krb5_realm_override.cc for details.
+  extra_env_["LD_PRELOAD"] = "libkrb5_realm_override.so";
+  if (getenv("LD_PRELOAD") != nullptr) {
+    extra_env_["LD_PRELOAD"] += ":" + string(getenv("LD_PRELOAD"));
+  }
+  extra_flags_.push_back(Substitute("--keytab=$0", ktpath));
+  extra_flags_.push_back(Substitute("--kerberos_principal=$0", spn));
   extra_flags_.push_back("--server_require_kerberos");
   return Status::OK();
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/integration-tests/external_mini_cluster.h
----------------------------------------------------------------------
diff --git a/src/kudu/integration-tests/external_mini_cluster.h b/src/kudu/integration-tests/external_mini_cluster.h
index 537c676..48f6325 100644
--- a/src/kudu/integration-tests/external_mini_cluster.h
+++ b/src/kudu/integration-tests/external_mini_cluster.h
@@ -328,8 +328,11 @@ class ExternalDaemon : public RefCountedThreadSafe<ExternalDaemon>
{
   // and keytab, and sets the appropriate environment variables in the
   // subprocess such that the server will use Kerberos authentication.
   //
+  // 'bind_host' is the hostname that will be used to generate the Kerberos
+  // service principal.
+  //
   // Must be called before 'StartProcess()'.
-  Status EnableKerberos(MiniKdc* kdc);
+  Status EnableKerberos(MiniKdc* kdc, const std::string& bind_host);
 
   // Sends a SIGSTOP signal to the daemon.
   Status Pause();

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/security/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/security/CMakeLists.txt b/src/kudu/security/CMakeLists.txt
index 28a9543..f0b4eff 100644
--- a/src/kudu/security/CMakeLists.txt
+++ b/src/kudu/security/CMakeLists.txt
@@ -15,6 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
+add_library(security
+  init.cc)
+target_link_libraries(security
+  gutil
+  kudu_util
+  krb5)
+
 set(SECURITY_TEST_SRCS
   test/mini_kdc.cc
 )
@@ -25,8 +32,15 @@ target_link_libraries(security-test
   kudu_test_util
   kudu_util)
 
+# See the comment in krb5_realm_override.cc for details on this library's usage.
+add_library(krb5_realm_override SHARED test/krb5_realm_override.cc)
+add_library(krb5_realm_override_static STATIC test/krb5_realm_override.cc)
+target_link_libraries(krb5_realm_override glog)
+target_link_libraries(krb5_realm_override_static glog)
+
 # Tests
 set(KUDU_TEST_LINK_LIBS
+  security
   security-test
   ${KUDU_MIN_TEST_LIBS})
 

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/security/init.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/init.cc b/src/kudu/security/init.cc
new file mode 100644
index 0000000..5d0ded2
--- /dev/null
+++ b/src/kudu/security/init.cc
@@ -0,0 +1,151 @@
+// 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 "kudu/security/init.h"
+
+#include <krb5/krb5.h>
+#include <string>
+
+#include "kudu/gutil/strings/util.h"
+#include "kudu/util/flags.h"
+#include "kudu/util/flag_tags.h"
+#include "kudu/util/net/net_util.h"
+#include "kudu/util/scoped_cleanup.h"
+
+DEFINE_string(keytab, "", "Path to the Kerberos Keytab for this server");
+TAG_FLAG(keytab, experimental);
+
+DEFINE_string(kerberos_principal, "kudu/_HOST",
+              "Kerberos principal that this daemon will log in as. The special token "
+              "_HOST will be replaced with the FQDN of the local host.");
+TAG_FLAG(kerberos_principal, experimental);
+// TODO(todd): this currently only affects the keytab login which is used
+// for client credentials, but doesn't affect the SASL server code path.
+// We probably need to plumb the same configuration into the RPC code.
+
+using std::string;
+
+namespace kudu {
+namespace security {
+
+namespace {
+
+Status Krb5CallToStatus(krb5_context ctx, krb5_error_code code) {
+  if (code == 0) return Status::OK();
+  return Status::RuntimeError(krb5_get_error_message(ctx, code));
+}
+#define KRB5_RETURN_NOT_OK_PREPEND(call, prepend) \
+  RETURN_NOT_OK_PREPEND(Krb5CallToStatus(ctx, (call)), (prepend))
+
+// Equivalent implementation of 'kinit -kt <keytab path> <principal>'.
+//
+// This logs in from the given keytab as the given principal, returning
+// RuntimeError if any part of this process fails.
+//
+// If the log-in is successful, then the default ticket cache is overwritten
+// with the credentials of the newly logged-in principal.
+Status Kinit(const string& keytab_path, const string& principal) {
+  krb5_context ctx;
+  if (krb5_init_context(&ctx) != 0) {
+    return Status::RuntimeError("could not initialize krb5 library");
+  }
+  auto cleanup_ctx = MakeScopedCleanup([&]() { krb5_free_context(ctx); });
+
+  // Parse the principal
+  krb5_principal client_principal;
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_parse_name(ctx, principal.c_str(), &client_principal),
+                             "could not parse principal");
+  auto cleanup_client_principal = MakeScopedCleanup([&]() {
+      krb5_free_principal(ctx, client_principal); });
+
+  krb5_keytab keytab;
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_kt_resolve(ctx, keytab_path.c_str(), &keytab),
+                             "unable to resolve keytab");
+  auto cleanup_keytab = MakeScopedCleanup([&]() { krb5_kt_close(ctx, keytab); });
+
+  krb5_ccache ccache;
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_cc_default(ctx, &ccache),
+                             "unable to get default credentials cache");
+  auto cleanup_ccache = MakeScopedCleanup([&]() { krb5_cc_close(ctx, ccache); });
+
+
+  krb5_get_init_creds_opt* opt;
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_get_init_creds_opt_alloc(ctx, &opt),
+                             "unable to allocate get_init_creds_opt struct");
+  auto cleanup_opt = MakeScopedCleanup([&]() { krb5_get_init_creds_opt_free(ctx, opt);
});
+
+#ifndef __APPLE__
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_get_init_creds_opt_set_out_ccache(ctx, opt, ccache),
+                             "unable to set init_creds options");
+#endif
+
+  krb5_creds creds;
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_get_init_creds_keytab(ctx, &creds, client_principal,
+                                                        keytab, 0 /* valid from now */,
+                                                        nullptr /* TKT service name */,
+                                                        opt),
+                             "unable to login from keytab");
+  auto cleanup_creds = MakeScopedCleanup([&]() { krb5_free_cred_contents(ctx, &creds);
});
+
+#ifdef __APPLE__
+  // Heimdal krb5 doesn't have the 'krb5_get_init_creds_opt_set_out_ccache' option,
+  // so use this alternate route.
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_cc_initialize(ctx, ccache, client_principal),
+                             "could not init ccache");
+
+  KRB5_RETURN_NOT_OK_PREPEND(krb5_cc_store_cred(ctx, ccache, &creds),
+                             "could not store creds in cache");
+#endif
+  return Status::OK();
+}
+
+Status GetLoginPrincipal(string* principal) {
+  string p = FLAGS_kerberos_principal;
+  string hostname;
+  // Try to fill in either the FQDN or hostname.
+  if (!GetFQDN(&hostname).ok()) {
+    RETURN_NOT_OK(GetHostname(&hostname));
+  }
+  GlobalReplaceSubstring("_HOST", hostname, &p);
+  *principal = p;
+  return Status::OK();
+}
+
+} // anonymous namespace
+
+Status InitKerberosForServer() {
+  if (FLAGS_keytab.empty()) return Status::OK();
+
+  // Have the daemons use an in-memory ticket cache, so they don't accidentally
+  // pick up credentials from test cases or any other daemon.
+  // TODO(todd): extract these krb5 env vars into some constants since they're
+  // typo-prone.
+  setenv("KRB5CCNAME", "MEMORY:kudu", 1);
+  setenv("KRB5_KTNAME", FLAGS_keytab.c_str(), 1);
+
+  string principal;
+  RETURN_NOT_OK(GetLoginPrincipal(&principal));
+  RETURN_NOT_OK_PREPEND(Kinit(FLAGS_keytab, principal), "unable to kinit");
+
+  // TODO(todd) we likely need to start a "renewal thread" here, since the credentials
+  // can expire.
+
+  return Status::OK();
+}
+
+} // namespace security
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/security/init.h
----------------------------------------------------------------------
diff --git a/src/kudu/security/init.h b/src/kudu/security/init.h
new file mode 100644
index 0000000..2497fcc
--- /dev/null
+++ b/src/kudu/security/init.h
@@ -0,0 +1,29 @@
+// 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 "kudu/util/status.h"
+
+namespace kudu {
+namespace security {
+
+// Initializes Kerberos for a server. In particular, this processes
+// the '--keytab' command line flag.
+Status InitKerberosForServer();
+
+} // namespace security
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/security/test/krb5_realm_override.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/test/krb5_realm_override.cc b/src/kudu/security/test/krb5_realm_override.cc
new file mode 100644
index 0000000..d506cf1
--- /dev/null
+++ b/src/kudu/security/test/krb5_realm_override.cc
@@ -0,0 +1,106 @@
+// 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.
+
+// This file provides a workaround for tests running with Kerberos 1.11 or earlier.
+// These versions of Kerberos are missing a fix which allows service principals
+// to use IP addresses in their host component:
+//
+//   http://krbdev.mit.edu/rt/Ticket/Display.html?id=7603
+//
+// We use such principals in external minicluster tests, where servers have IP addresses
+// like 127.x.y.z that have no corresponding reverse DNS.
+//
+// The file contains an implementation of krb5_get_host_realm which wraps the one
+// in the Kerberos library. It detects the return code that indicates the
+// above problem and falls back to the default realm/
+//
+// The wrapper is injected in two ways:
+// 1) kudu_test_main static-links against this source so that all test
+//    binaries load our wrapped function ahead of the library-provided one.
+// 2) ExternalMiniCluster sets LD_PRELOAD to load a shared library built from
+//    this source before it forks server processes.
+//
+// Thus, we don't end up polluting our shipped binaries with this wrapper, but
+// our tests which depend on numeric host names can still pass on Kerberos 1.10.
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <krb5/krb5.h>
+#include <glog/logging.h>
+
+extern "C" {
+
+// This symbol is exported from the static library so that
+// test_main.cc can reference it and force this compilation unit
+// to be linked. Otherwise the linker thinks it's unused and doesn't
+// link it.
+int krb5_realm_override_loaded = 1;
+
+// Save the original function from the Kerberos library itself.
+// We use dlsym() to load all of them, since this file gets linked into
+// some test binaries that themselves may not link against libkrb5.so at all.
+static void* g_orig_krb5_get_host_realm;
+static void* g_orig_krb5_get_default_realm;
+static void* g_orig_krb5_free_default_realm;
+
+#define CALL_ORIG(func_name, ...) \
+  ((decltype(&func_name))g_orig_ ## func_name)(__VA_ARGS__)
+
+__attribute__((constructor))
+static void init_orig_func() {
+  g_orig_krb5_get_host_realm = dlsym(RTLD_NEXT, "krb5_get_host_realm");
+  g_orig_krb5_get_default_realm = dlsym(RTLD_NEXT, "krb5_get_default_realm");
+  g_orig_krb5_free_default_realm = dlsym(RTLD_NEXT, "krb5_free_default_realm");
+}
+
+krb5_error_code krb5_get_host_realm(krb5_context context, const char* host, char*** realmsp)
{
+  CHECK(g_orig_krb5_get_host_realm);
+  CHECK(g_orig_krb5_get_default_realm);
+  CHECK(g_orig_krb5_free_default_realm);
+
+  krb5_error_code rc = CALL_ORIG(krb5_get_host_realm, context, host, realmsp);
+  if (rc != KRB5_ERR_NUMERIC_REALM) {
+    return rc;
+  }
+  // If we get KRB5_ERR_NUMERIC_REALM, this is indicative of a Kerberos version
+  // which has not provided support for numeric addresses as service host names
+  // So, we fill in the default realm instead.
+  char* default_realm;
+  rc = CALL_ORIG(krb5_get_default_realm, context, &default_realm);
+  if (rc != 0) {
+    return rc;
+  }
+
+  char** ret_realms;
+  ret_realms = static_cast<char**>(malloc(2 * sizeof(*ret_realms)));
+  if (ret_realms == nullptr) return ENOMEM;
+  ret_realms[0] = strdup(default_realm);
+  if (ret_realms[0] == nullptr) {
+    free(ret_realms);
+    return ENOMEM;
+  }
+  ret_realms[1] = 0;
+  *realmsp = ret_realms;
+
+  CALL_ORIG(krb5_free_default_realm, context, default_realm);
+  return 0;
+}
+
+} // extern "C"

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/security/test/mini_kdc-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/security/test/mini_kdc-test.cc b/src/kudu/security/test/mini_kdc-test.cc
index 535c5d7..62bda5c 100644
--- a/src/kudu/security/test/mini_kdc-test.cc
+++ b/src/kudu/security/test/mini_kdc-test.cc
@@ -17,17 +17,24 @@
 
 #include <string>
 
+#include <gflags/gflags.h>
 #include <gtest/gtest.h>
 
+#include "kudu/security/init.h"
 #include "kudu/security/test/mini_kdc.h"
 #include "kudu/util/env.h"
 #include "kudu/util/test_util.h"
 
 using std::string;
 
+DECLARE_string(keytab);
+DECLARE_string(kerberos_principal);
+
 namespace kudu {
 
-TEST(MiniKdcTest, TestBasicOperation) {
+class MiniKdcTest : public KuduTest {};
+
+TEST_F(MiniKdcTest, TestBasicOperation) {
   MiniKdcOptions options;
   MiniKdc kdc(options);
   ASSERT_OK(kdc.Start());
@@ -58,15 +65,22 @@ TEST(MiniKdcTest, TestBasicOperation) {
   ASSERT_TRUE(kdc.Klist(&klist).IsRuntimeError());
 
   // Test keytab creation.
+  const string kSPN = "kudu/foo.example.com";
   string kt_path;
-  ASSERT_OK(kdc.CreateServiceKeytab("kudu/foo.example.com", &kt_path));
+  ASSERT_OK(kdc.CreateServiceKeytab(kSPN, &kt_path));
   SCOPED_TRACE(kt_path);
   ASSERT_OK(kdc.KlistKeytab(kt_path, &klist));
   ASSERT_STR_CONTAINS(klist, "kudu/foo.example.com@KRBTEST.COM");
+
+  // Test programmatic keytab login.
+  kdc.SetKrb5Environment();
+  FLAGS_keytab = kt_path;
+  FLAGS_kerberos_principal = kSPN;
+  ASSERT_OK(security::InitKerberosForServer());
 }
 
 // Regression test to ensure that dropping a stopped MiniKdc doesn't panic.
-TEST(MiniKdcTest, TestStopDrop) {
+TEST_F(MiniKdcTest, TestStopDrop) {
   MiniKdcOptions options;
   MiniKdc kdc(options);
 }

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/server/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/server/CMakeLists.txt b/src/kudu/server/CMakeLists.txt
index 5f67892..1cdb0f8 100644
--- a/src/kudu/server/CMakeLists.txt
+++ b/src/kudu/server/CMakeLists.txt
@@ -31,7 +31,8 @@ target_link_libraries(server_common
   gutil
   kudu_fs
   kudu_util
-  consensus_metadata_proto)
+  consensus_metadata_proto
+  security)
 
 #########################################
 # server_common tests

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/server/server_base.cc
----------------------------------------------------------------------
diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc
index bbef89b..463fc59 100644
--- a/src/kudu/server/server_base.cc
+++ b/src/kudu/server/server_base.cc
@@ -30,6 +30,7 @@
 #include "kudu/gutil/strings/substitute.h"
 #include "kudu/gutil/walltime.h"
 #include "kudu/rpc/messenger.h"
+#include "kudu/security/init.h"
 #include "kudu/server/default-path-handlers.h"
 #include "kudu/server/generic_service.h"
 #include "kudu/server/glog_metrics.h"
@@ -168,6 +169,8 @@ Status ServerBase::Init() {
   // if we're having clock problems.
   RETURN_NOT_OK_PREPEND(clock_->Init(), "Cannot initialize clock");
 
+  RETURN_NOT_OK(security::InitKerberosForServer());
+
   Status s = fs_manager_->Open();
   if (s.IsNotFound()) {
     LOG(INFO) << "Could not load existing FS layout: " << s.ToString();

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/util/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/util/CMakeLists.txt b/src/kudu/util/CMakeLists.txt
index 667942d..1fcefa5 100644
--- a/src/kudu/util/CMakeLists.txt
+++ b/src/kudu/util/CMakeLists.txt
@@ -266,7 +266,9 @@ target_link_libraries(kudu_test_main
   glog
   gmock
   kudu_util
-  kudu_test_util)
+  kudu_test_util
+  # See the comment in kudu/security/test/krb5_realm_override.cc for details.
+  krb5_realm_override_static)
 
 if(NOT APPLE)
   target_link_libraries(kudu_test_main

http://git-wip-us.apache.org/repos/asf/kudu/blob/ba2ae3de/src/kudu/util/test_main.cc
----------------------------------------------------------------------
diff --git a/src/kudu/util/test_main.cc b/src/kudu/util/test_main.cc
index 2935b24..9d8e4a8 100644
--- a/src/kudu/util/test_main.cc
+++ b/src/kudu/util/test_main.cc
@@ -34,6 +34,13 @@ DEFINE_int32(stress_cpu_threads, 0,
              "Number of threads to start that burn CPU in an attempt to "
              "stimulate race conditions");
 
+// Force linking of krb5_realm_override.cc. Without using any of its
+// symbols explicitly, the compilation unit would be skipped.
+extern "C" {
+extern int krb5_realm_override_loaded;
+int force_load_krb5_workaround = krb5_realm_override_loaded;
+}
+
 namespace kudu {
 
 // Start thread that kills the process if --test_timeout_after is exceeded before


Mime
View raw message