Skip to content

Commit

Permalink
ZOOKEEPER-1112: Add (Cyrus) SASL authentication support to C client l…
Browse files Browse the repository at this point in the history
…ibrary

This changeset allows C clients to use SASL to authenticate with the
ZooKeeper server.  It is loosely based on patches apache#1 and apache#2 by Tom
Klonikowski, at https://reviews.apache.org/r/2252/, but the result has
been extensively reworked to follow the semantics of the Java client:

  * No SASL operations are exposed through the API;

  * The configuration is provided, and stored, at "handle init time";

  * SASL authentication is automatically performed after each
    (re)connect.

It introduces an optional dependency on the Cyrus SASL library, which
can either be autodetected (default) or configured using the
--without-sasl/--with-sasl[=DIR] flags.

TestServerRequireClientSASLAuth.cc has been renamed to TestSASLAuth.cc,
and a test has been added which successfully (re)authenticates using
the DIGEST-MD5 mechanism.  An earlier version of this code has been
used to successfully authenticate clients via Kerberos.

While cli.c is not modified by this commit, we are planning to submit
a subsequent contribution which enables SASL support in that client
using the ZOOKEEPER-3599 (use getopt if available) mechanism.

Co-authored-by: Tom Klonikowski <[email protected]>
  • Loading branch information
ztzg and Tom Klonikowski committed Nov 6, 2019
1 parent e41cac8 commit 7d8991a
Show file tree
Hide file tree
Showing 11 changed files with 1,160 additions and 142 deletions.
28 changes: 19 additions & 9 deletions zookeeper-client/zookeeper-client-c/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ endif

LIB_LDFLAGS = -no-undefined -version-info 2 $(SOLARIS_LIB_LDFLAGS)

if WANT_SASL
SASL_CFLAGS = -DZK_SASL
SASL_LIBS = -lsasl2
SASL_SRC = src/zk_sasl.c
endif

pkginclude_HEADERS = include/zookeeper.h include/zookeeper_version.h include/zookeeper_log.h include/proto.h include/recordio.h generated/zookeeper.jute.h
EXTRA_DIST=LICENSE

Expand All @@ -31,28 +37,31 @@ COMMON_SRC = src/zookeeper.c include/zookeeper.h include/zookeeper_version.h inc
src/recordio.c include/recordio.h include/proto.h \
src/zk_adaptor.h generated/zookeeper.jute.c \
src/zk_log.c src/zk_hashtable.h src/zk_hashtable.c \
src/addrvec.h src/addrvec.c
src/addrvec.h src/addrvec.c $(SASL_SRC)

# These are the symbols (classes, mostly) we want to export from our library.
EXPORT_SYMBOLS = '(zoo_|zookeeper_|zhandle|Z|format_log_message|log_message|logLevel|deallocate_|allocate_|zerror|is_unrecoverable)'
noinst_LTLIBRARIES += libzkst.la
libzkst_la_SOURCES =$(COMMON_SRC) src/st_adaptor.c
libzkst_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS)
libzkst_la_CFLAGS = $(SASL_CFLAGS)
libzkst_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS) $(SASL_LIBS)

lib_LTLIBRARIES = libzookeeper_st.la
libzookeeper_st_la_SOURCES =
libzookeeper_st_la_CFLAGS = $(SASL_CFLAGS)
libzookeeper_st_la_LIBADD=libzkst.la libhashtable.la
libzookeeper_st_la_DEPENDENCIES=libzkst.la libhashtable.la
libzookeeper_st_la_LDFLAGS = $(LIB_LDFLAGS) -export-symbols-regex $(EXPORT_SYMBOLS)

if WANT_SYNCAPI
noinst_LTLIBRARIES += libzkmt.la
libzkmt_la_SOURCES =$(COMMON_SRC) src/mt_adaptor.c
libzkmt_la_CFLAGS = -DTHREADED
libzkmt_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS)
libzkmt_la_CFLAGS = -DTHREADED $(SASL_CFLAGS)
libzkmt_la_LIBADD = -lm $(CLOCK_GETTIME_LIBS) $(SASL_LIBS)

lib_LTLIBRARIES += libzookeeper_mt.la
libzookeeper_mt_la_SOURCES =
libzookeeper_mt_la_CFLAGS = -DTHREADED $(SASL_CFLAGS)
libzookeeper_mt_la_LIBADD=libzkmt.la libhashtable.la -lpthread
libzookeeper_mt_la_DEPENDENCIES=libzkmt.la libhashtable.la
libzookeeper_mt_la_LDFLAGS = $(LIB_LDFLAGS) -export-symbols-regex $(EXPORT_SYMBOLS)
Expand All @@ -62,17 +71,18 @@ bin_PROGRAMS = cli_st

cli_st_SOURCES = src/cli.c
cli_st_LDADD = libzookeeper_st.la
cli_st_CFLAGS = $(SASL_CFLAGS)

if WANT_SYNCAPI
bin_PROGRAMS += cli_mt load_gen

cli_mt_SOURCES = src/cli.c
cli_mt_LDADD = libzookeeper_mt.la
cli_mt_CFLAGS = -DTHREADED
cli_mt_CFLAGS = -DTHREADED $(SASL_CFLAGS)

load_gen_SOURCES = src/load_gen.c
load_gen_LDADD = libzookeeper_mt.la
load_gen_CFLAGS = -DTHREADED
load_gen_CFLAGS = -DTHREADED $(SASL_CFLAGS)

endif

Expand Down Expand Up @@ -107,7 +117,7 @@ TEST_SOURCES = \
tests/ZooKeeperQuorumServer.h \
tests/TestReadOnlyClient.cc \
tests/TestLogClientEnv.cc \
tests/TestServerRequireClientSASLAuth.cc \
tests/TestSASLAuth.cc \
$(NULL)

if SOLARIS
Expand All @@ -122,14 +132,14 @@ TESTS_ENVIRONMENT = ZKROOT=${srcdir}/../.. \
CLASSPATH=$$CLASSPATH:$$CLOVER_HOME/lib/clover*.jar
nodist_zktest_st_SOURCES = $(TEST_SOURCES)
zktest_st_LDADD = libzkst.la libhashtable.la $(CPPUNIT_LIBS) -ldl
zktest_st_CXXFLAGS = -DUSE_STATIC_LIB $(CPPUNIT_CFLAGS) $(USEIPV6) $(SOLARIS_CPPFLAGS)
zktest_st_CXXFLAGS = -DUSE_STATIC_LIB $(CPPUNIT_CFLAGS) $(USEIPV6) $(SASL_CFLAGS) $(SOLARIS_CPPFLAGS)
zktest_st_LDFLAGS = -shared $(SYMBOL_WRAPPERS) $(SOLARIS_LIB_LDFLAGS)

if WANT_SYNCAPI
check_PROGRAMS += zktest-mt
nodist_zktest_mt_SOURCES = $(TEST_SOURCES) tests/PthreadMocks.cc
zktest_mt_LDADD = libzkmt.la libhashtable.la -lpthread $(CPPUNIT_LIBS) -ldl
zktest_mt_CXXFLAGS = -DUSE_STATIC_LIB -DTHREADED $(CPPUNIT_CFLAGS) $(USEIPV6)
zktest_mt_CXXFLAGS = -DUSE_STATIC_LIB -DTHREADED $(CPPUNIT_CFLAGS) $(USEIPV6) $(SASL_CFLAGS)
if SOLARIS
SHELL_SYMBOL_WRAPPERS_MT = cat ${srcdir}/tests/wrappers-mt.opt
SYMBOL_WRAPPERS_MT=$(SYMBOL_WRAPPERS) $(SHELL_SYMBOL_WRAPPERS_MT:sh)
Expand Down
30 changes: 30 additions & 0 deletions zookeeper-client/zookeeper-client-c/configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,36 @@ fi

AM_CONDITIONAL([WANT_SYNCAPI],[test "x$with_syncapi" != xno])

dnl Cyrus SASL
AC_ARG_WITH(sasl,
[AC_HELP_STRING([--with-sasl[=DIR]], [build with support for SASL (default=auto)])],
[], [with_sasl=yes])
if test "x$with_sasl" != "xno"; then
saved_CPPFLAGS="$CPPFLAGS"
saved_LDFLAGS="$LDFLAGS"
if test "x$with_sasl" != "xyes" ; then
CPPFLAGS="$CPPFLAGS -I$with_sasl/include"
LDFLAGS="$LDFLAGS -L$with_sasl/lib"
fi
have_sasl=no
AC_CHECK_HEADER(sasl/sasl.h, [
AC_CHECK_LIB(sasl2, sasl_client_init, [have_sasl=yes])])
if test "x$have_sasl" != "xyes"; then
CPPFLAGS="$saved_CPPFLAGS"
LDFLAGS="$saved_LDFLAGS"
fi
fi
if test "x$with_sasl" != xno && test "x$have_sasl" = xno; then
AC_MSG_WARN([cannot build SASL support -- sasl2 not found])
with_sasl=no
fi
if test "x$with_sasl" != xno; then
AC_MSG_NOTICE([building with SASL support])
else
AC_MSG_NOTICE([building without SASL support])
fi
AM_CONDITIONAL([WANT_SASL],[test "x$with_sasl" != xno])

# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h sys/socket.h sys/time.h unistd.h sys/utsname.h])
Expand Down
1 change: 1 addition & 0 deletions zookeeper-client/zookeeper-client-c/include/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ extern "C" {
#define ZOO_CLOSE_OP -11
#define ZOO_SETAUTH_OP 100
#define ZOO_SETWATCHES_OP 101
#define ZOO_SASL_OP 102

#ifdef __cplusplus
}
Expand Down
91 changes: 91 additions & 0 deletions zookeeper-client/zookeeper-client-c/include/zookeeper.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#include <stdio.h>
#include <ctype.h>

#ifdef ZK_SASL
#include <sasl/sasl.h>
#endif /* ZK_SASL */

#include "proto.h"
#include "zookeeper_version.h"
#include "recordio.h"
Expand Down Expand Up @@ -535,6 +539,93 @@ ZOOAPI zhandle_t *zookeeper_init2(const char *host, watcher_fn fn,
int recv_timeout, const clientid_t *clientid, void *context, int flags,
log_callback_fn log_callback);

#ifdef ZK_SASL

/**
* \brief zoo_sasl_params structure.
*
* This structure holds the SASL parameters for the connection.
*
* Its \c service, \c host and \c callbacks fields are used with Cyrus
* SASL's \c sasl_client_new; its \c mechlist field with \c
* sasl_client_start. Please refer to these functions for precise
* semantics.
*
* Note while "string" parameters are copied into the ZooKeeper
* client, the callbacks array is simply referenced: its lifetime must
* therefore cover that of the handle.
*/
typedef struct zoo_sasl_params {
const char *service; /*!< The service name, usually "zookeeper" */
const char *host; /*!< The server name, e.g. "zk-sasl-md5" */
const char *mechlist; /*!< Mechanisms to try, e.g. "DIGEST-MD5" */
const sasl_callback_t *callbacks; /*!< List of callbacks */
} zoo_sasl_params_t;

/**
* \brief create a handle to communicate with zookeeper.
*
* This function is identical to \ref zookeeper_init2 except that it
* allows specifying optional SASL connection parameters. It is only
* available if the client library was configured to link against the
* Cyrus SASL library, and only visible when \c ZK_SASL is defined.
*
* This method creates a new handle and a zookeeper session that corresponds
* to that handle. Session establishment is asynchronous, meaning that the
* session should not be considered established until (and unless) an
* event of state ZOO_CONNECTED_STATE is received.
* \param host comma separated host:port pairs, each corresponding to a zk
* server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
* \param fn the global watcher callback function. When notifications are
* triggered this function will be invoked.
* \param clientid the id of a previously established session that this
* client will be reconnecting to. Pass 0 if not reconnecting to a previous
* session. Clients can access the session id of an established, valid,
* connection by calling \ref zoo_client_id. If the session corresponding to
* the specified clientid has expired, or if the clientid is invalid for
* any reason, the returned zhandle_t will be invalid -- the zhandle_t
* state will indicate the reason for failure (typically
* ZOO_EXPIRED_SESSION_STATE).
* \param context the handback object that will be associated with this instance
* of zhandle_t. Application can access it (for example, in the watcher
* callback) using \ref zoo_get_context. The object is not used by zookeeper
* internally and can be null.
* \param flags reserved for future use. Should be set to zero.
* \param log_callback All log messages will be passed to this callback function.
* For more details see \ref zoo_get_log_callback and \ref zoo_set_log_callback.
* \param sasl_params a pointer to a \ref zoo_sasl_params_t structure
* specifying SASL connection parameters, or NULL to skip SASL
* authentication
* \return a pointer to the opaque zhandle structure. If it fails to create
* a new zhandle the function returns NULL and the errno variable
* indicates the reason.
*/
ZOOAPI zhandle_t *zookeeper_init_sasl(const char *host, watcher_fn fn,
int recv_timeout, const clientid_t *clientid, void *context, int flags,
log_callback_fn log_callback, zoo_sasl_params_t *sasl_params);

/**
* \brief allocates and initializes a basic array of Cyrus SASL callbacks.
*
* This small helper function makes it easy to pass "static"
* parameters to Cyrus SASL's underlying callback-based API. Its use
* is not mandatory; you can still implement interactive dialogs by
* defining your own callbacks.
*
* \param user the "canned" response to \c SASL_CB_USER and \c SASL_CB_AUTHNAME,
* or NULL for none
* \param realm the "canned" response to \c SASL_CB_GETREALM, or NULL for none
* \param password_file the name of a file whose first line is read in
* response to \c SASL_CB_PASS, or NULL for none
* \return the freshly-malloc()ed callbacks array, or NULL if allocation
* failed. Deallocate with free(), but only after the corresponding
* ZooKeeper handle is closed.
*/
ZOOAPI sasl_callback_t *zoo_sasl_make_basic_callbacks(const char *user,
const char *realm, const char* password_file);

#endif /* ZK_SASL */

/**
* \brief update the list of servers this client will connect to.
*
Expand Down
7 changes: 7 additions & 0 deletions zookeeper-client/zookeeper-client-c/src/zk_adaptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#ifndef ZK_ADAPTOR_H_
#define ZK_ADAPTOR_H_

#include <zookeeper.jute.h>
#ifdef THREADED
#ifndef WIN32
Expand Down Expand Up @@ -181,6 +182,8 @@ typedef struct _auth_list_head {
#endif
} auth_list_head_t;

typedef struct _zoo_sasl_client zoo_sasl_client_t;

/**
* This structure represents the connection to zookeeper.
*/
Expand Down Expand Up @@ -264,6 +267,10 @@ struct _zhandle {
/** used for chroot path at the client side **/
char *chroot;

#ifdef ZK_SASL
zoo_sasl_client_t *sasl_client;
#endif /* ZK_SASL */

/** Indicates if this client is allowed to go to r/o mode */
char allow_read_only;
/** Indicates if we connected to a majority server before */
Expand Down
Loading

0 comments on commit 7d8991a

Please sign in to comment.