From 4fde5736efa6f9bf9eaadb8ce300e79913e69e89 Mon Sep 17 00:00:00 2001 From: Tom Klonikowski Date: Sun, 9 Oct 2011 16:26:00 +0200 Subject: [PATCH] ZOOKEEPER-1112: Add support for C client for SASL authentication Patch #1 This first patch implements the zookeeper sasl operations, extends the zkServer.sh test script with the ability to start a sasl enabled server and adds a test that checks the initial DIGEST-MD5 response. It introduces no external requirements but provides zoo_sasl/zoo_asasl functions that allow applications (or the addon from patch #2) to communicate with the sasl server backend. Patch #2 will add a simple api for sasl authentication, patch #3 includes a sasl enabled command line client. (Forward-ported from https://reviews.apache.org/r/2252/ by Damien Diederen.) --- .../zookeeper-client-c/include/proto.h | 1 + .../zookeeper-client-c/include/zookeeper.h | 38 ++++++ .../zookeeper-client-c/src/zk_adaptor.h | 4 + .../zookeeper-client-c/src/zookeeper.c | 127 ++++++++++++++++++ .../zookeeper-client-c/tests/TestClient.cc | 53 ++++++++ .../tests/jaas.digest.server.conf | 4 + .../zookeeper-client-c/tests/zkServer.sh | 23 +++- 7 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 zookeeper-client/zookeeper-client-c/tests/jaas.digest.server.conf diff --git a/zookeeper-client/zookeeper-client-c/include/proto.h b/zookeeper-client/zookeeper-client-c/include/proto.h index 88774ff782e..65afde94d81 100644 --- a/zookeeper-client/zookeeper-client-c/include/proto.h +++ b/zookeeper-client/zookeeper-client-c/include/proto.h @@ -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 } diff --git a/zookeeper-client/zookeeper-client-c/include/zookeeper.h b/zookeeper-client/zookeeper-client-c/include/zookeeper.h index 304dafe2f69..513ba08fe7c 100644 --- a/zookeeper-client/zookeeper-client-c/include/zookeeper.h +++ b/zookeeper-client/zookeeper-client-c/include/zookeeper.h @@ -1676,6 +1676,29 @@ ZOOAPI int zoo_aremove_all_watches(zhandle_t *zh, const char *path, ZooWatcherType wtype, int local, void_completion_t *completion, const void *data); +typedef struct zoo_sasl_conn zoo_sasl_conn_t; + +typedef int (*sasl_completion_t)(int rc, zhandle_t *zh, zoo_sasl_conn_t *conn, + const char *serverin, int serverinlen); + +/** + * \brief send a sasl request asynchronously. + * + * \param zh the zookeeper handle obtained by a call to \ref zookeeper_init + * \param zh the connection handle obtained by a call to \ref zoo_sasl_connect + * \param clientout the token + * \param clientoutlen the token length + * \param cptr function to call with the server response + * \return ZMARSHALLINGERROR if sending failed, ZOK otherwise + */ +ZOOAPI int zoo_asasl(zhandle_t *zh, zoo_sasl_conn_t *conn, const char *clientout, + unsigned clientoutlen, sasl_completion_t cptr); + +struct sasl_completion_ctx { + zhandle_t *zh; + zoo_sasl_conn_t *conn; +}; + #ifdef THREADED /** * \brief create a node synchronously. @@ -2288,7 +2311,22 @@ ZOOAPI int zoo_multi(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_resul */ ZOOAPI int zoo_remove_watches(zhandle_t *zh, const char *path, ZooWatcherType wtype, watcher_fn watcher, void *watcherCtx, int local); + +/** + * \brief send a sasl request synchronously. + * + * \param zh the zookeeper handle obtained by a call to \ref zookeeper_init + * \param zh the connection handle obtained by a call to \ref zoo_sasl_connect + * \param clientout the token to send + * \param clientoutlen the token length + * \param serverin the received token + * \param serverinlen the token length + * \return + */ +ZOOAPI int zoo_sasl(zhandle_t *zh, zoo_sasl_conn_t *conn, const char *clientout, + unsigned clientoutlen, const char **serverin, unsigned *serverinlen); #endif + #ifdef __cplusplus } #endif diff --git a/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h b/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h index 97995e36ace..90984088e4e 100644 --- a/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h +++ b/zookeeper-client/zookeeper-client-c/src/zk_adaptor.h @@ -104,6 +104,10 @@ struct sync_completion { struct String_vector strs2; struct Stat stat2; } strs_stat; + struct { + char *token; + int token_len; + } sasl; } u; int complete; #ifdef THREADED diff --git a/zookeeper-client/zookeeper-client-c/src/zookeeper.c b/zookeeper-client/zookeeper-client-c/src/zookeeper.c index 70762c29b35..06ffeca50b0 100644 --- a/zookeeper-client/zookeeper-client-c/src/zookeeper.c +++ b/zookeeper-client/zookeeper-client-c/src/zookeeper.c @@ -197,6 +197,7 @@ struct ACL_vector ZOO_CREATOR_ALL_ACL = { 1, _CREATOR_ALL_ACL_ACL}; #define COMPLETION_STRING 6 #define COMPLETION_MULTI 7 #define COMPLETION_STRING_STAT 8 +#define COMPLETION_SASL 9 typedef struct _auth_completion_list { void_completion_t completion; @@ -215,6 +216,7 @@ typedef struct completion { acl_completion_t acl_result; string_completion_t string_result; string_stat_completion_t string_stat_result; + sasl_completion_t sasl_result; struct watcher_object_list *watcher_result; }; completion_head_t clist; /* For multi-op */ @@ -1711,6 +1713,17 @@ void free_completions(zhandle_t *zh,int callCompletion,int reason) destroy_completion_entry(cptr); #else abort_singlethreaded(zh); +#endif + } else if (cptr->c.sasl_result == SYNCHRONOUS_MARKER) { +#ifdef THREADED + struct sync_completion + *sc = (struct sync_completion*)cptr->data; + sc->rc = reason; + notify_sync_completion(sc); + zh->outstanding_sync--; + destroy_completion_entry(cptr); +#else + abort_singlethreaded(zh); #endif } else if (callCompletion) { // Fake the response @@ -2763,6 +2776,23 @@ static void deserialize_response(zhandle_t *zh, int type, int xid, int failed, i } cptr->c.void_result(rc, cptr->data); break; + case COMPLETION_SASL: + LOG_DEBUG(LOGCALLBACK(zh), "Calling COMPLETION_SASL for xid=%#x failed=%d rc=%d", + cptr->xid, failed, rc); + if (failed) { + struct sasl_completion_ctx *sctx = + (struct sasl_completion_ctx *) cptr->data; + cptr->c.sasl_result(rc, sctx->zh, sctx->conn, NULL, 0); + } else { + struct sasl_completion_ctx *sctx = + (struct sasl_completion_ctx *) cptr->data; + struct SetSASLResponse res; + deserialize_SetSASLResponse(ia, "reply", &res); + cptr->c.sasl_result(rc, sctx->zh, sctx->conn, + res.token.buff, res.token.len); + deallocate_SetSASLResponse(&res); + } + break; default: LOG_DEBUG(LOGCALLBACK(zh), "Unsupported completion type=%d", cptr->c.type); } @@ -3082,6 +3112,9 @@ static completion_list_t* do_create_completion_entry(zhandle_t *zh, int xid, c->c.void_result = (void_completion_t)dc; c->c.clist = *clist; break; + case COMPLETION_SASL: + c->c.sasl_result = (sasl_completion_t) dc; + break; } c->xid = xid; c->watcher = wo; @@ -3226,6 +3259,12 @@ static int add_multi_completion(zhandle_t *zh, int xid, void_completion_t dc, return add_completion(zh, xid, COMPLETION_MULTI, dc, data, 0,0, clist); } +static int add_sasl_completion(zhandle_t *zh, int xid, sasl_completion_t dc, + const void *data, completion_head_t *clist) +{ + return add_completion(zh, xid, COMPLETION_SASL, dc, data, 0,0, clist); +} + int zookeeper_close(zhandle_t *zh) { int rc=ZOK; @@ -4248,6 +4287,48 @@ static int aremove_watches( free_duplicate_path(server_path, path); return rc; } + +static int queue_sasl_request(zhandle_t *zh, const char *data, unsigned len, void* cptr, + void *ctx) { + struct oarchive *oa; + int rc; + + struct RequestHeader h = { get_xid(), ZOO_SASL_OP }; + struct GetSASLRequest req = { { len, len>0 ? (char *) data : "" } }; + + LOG_DEBUG(LOGCALLBACK(zh), "saslToken (client) length: %d", len); + + oa = create_buffer_oarchive(); + rc = serialize_RequestHeader(oa, "header", &h); + rc = rc < 0 ? rc : serialize_GetSASLRequest(oa, "req", &req); + + enter_critical(zh); + rc = rc < 0 ? rc : add_sasl_completion(zh, h.xid, cptr, ctx, NULL); + rc = rc < 0 ? rc : queue_buffer_bytes(&zh->to_send, get_buffer(oa), + get_buffer_len(oa)); + leave_critical(zh); + close_buffer_oarchive(&oa, 0); + + LOG_DEBUG(LOGCALLBACK(zh), "Sending sasl token request xid=%#x to %s", h.xid, zoo_get_current_server(zh)); + adaptor_send_queue(zh, 0); + return (rc < 0) ? ZMARSHALLINGERROR : ZOK; +} + +int zoo_asasl(zhandle_t *zh, zoo_sasl_conn_t *conn, const char *clientout, + unsigned clientoutlen, sasl_completion_t cptr) { + int r; + struct sasl_completion_ctx *sctx = + (struct sasl_completion_ctx *) malloc( + sizeof(struct sasl_completion_ctx)); + sctx->zh = zh; + sctx->conn = conn; + + r = queue_sasl_request(zh, clientout, clientoutlen, cptr, sctx); + free(sctx); + + return r; +} + void zoo_create_op_init(zoo_op_t *op, const char *path, const char *value, int valuelen, const struct ACL_vector *acl, int mode, char *path_buffer, int path_buffer_len) @@ -4667,6 +4748,25 @@ static void process_sync_completion(zhandle_t *zh, case COMPLETION_MULTI: sc->rc = deserialize_multi(zh, cptr->xid, cptr, ia); break; + case COMPLETION_SASL: + if (sc->rc==0) { + struct SetSASLResponse res; + int len; + deserialize_SetSASLResponse(ia, "reply", &res); + if (res.token.len <= sc->u.sasl.token_len) { + len = res.token.len; + } else { + len = sc->u.data.buff_len; + } + sc->u.sasl.token_len = len; + if (len == -1) { + sc->u.sasl.token = NULL; + } else { + memcpy(sc->u.sasl.token, res.token.buff, len); + } + deallocate_SetSASLResponse(&res); + } + break; default: LOG_DEBUG(LOGCALLBACK(zh), "Unsupported completion type=%d", cptr->c.type); break; @@ -5058,6 +5158,33 @@ int zoo_remove_all_watches( return remove_watches(zh, path, wtype, NULL, NULL, local, 1); } + +int zoo_sasl(zhandle_t *zh, zoo_sasl_conn_t *conn, const char *clientout, + unsigned clientoutlen, const char **serverin, unsigned *serverinlen) { + int rc; + char buf[8192]; + + struct sync_completion *sc = alloc_sync_completion(); + sc->u.sasl.token = buf; + sc->u.sasl.token_len = sizeof(buf); + + rc = queue_sasl_request(zh, clientout, clientoutlen, SYNCHRONOUS_MARKER, sc); + + if(rc==ZOK){ + wait_sync_completion(sc); + rc = sc->rc; + if(rc == ZOK && sc->u.sasl.token_len > 0) { + *serverin = sc->u.sasl.token; + *serverinlen = sc->u.sasl.token_len; + } else { + serverinlen = 0; + *serverin = NULL; + } + } + free_sync_completion(sc); + + return rc; +} #endif int zoo_aremove_watches(zhandle_t *zh, const char *path, ZooWatcherType wtype, diff --git a/zookeeper-client/zookeeper-client-c/tests/TestClient.cc b/zookeeper-client/zookeeper-client-c/tests/TestClient.cc index 6bcfe148672..743d481a4b1 100644 --- a/zookeeper-client/zookeeper-client-c/tests/TestClient.cc +++ b/zookeeper-client/zookeeper-client-c/tests/TestClient.cc @@ -225,6 +225,7 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture CPPUNIT_TEST(testGetChildren2); CPPUNIT_TEST(testLastZxid); CPPUNIT_TEST(testRemoveWatchers); + CPPUNIT_TEST(testSasl); #endif CPPUNIT_TEST_SUITE_END(); @@ -301,6 +302,12 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture CPPUNIT_ASSERT(system(cmd) == 0); } + void startServerWithOpts(const char *opts) { + char cmd[1024]; + sprintf(cmd, "%s start %s %s", ZKSERVER_CMD, getHostPorts(), opts); + CPPUNIT_ASSERT(system(cmd) == 0); + } + void stopServer() { char cmd[1024]; sprintf(cmd, "%s stop %s", ZKSERVER_CMD, getHostPorts()); @@ -491,6 +498,19 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture count++; } + static int saslDigestInitCompletion(int rc, zhandle_t *zh, zoo_sasl_conn_t *conn, + const char *serverin, int serverinlen) { + const char *realm = "realm"; + const char *nonce = "nonce"; + CPPUNIT_ASSERT_EQUAL((int) ZOK, rc); + // response should look like + // realm="zk-sasl-md5",nonce="4n7iytvP7E9GyRVvGQ8pATPPnXJ0GjOB5rmTzk3a",... + //LOG_DEBUG(("SASL Response: %s", serverin)); + CPPUNIT_ASSERT(strstr(serverin, realm)!=NULL); + CPPUNIT_ASSERT(strstr(serverin, nonce)!=NULL); + return rc; + } + static void verifyCreateFails(const char *path, zhandle_t *zk) { CPPUNIT_ASSERT_EQUAL((int)ZBADARGUMENTS, zoo_create(zk, path, "", 0, &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0)); @@ -1464,6 +1484,39 @@ class Zookeeper_simpleSystem : public CPPUNIT_NS::TestFixture watcher_rw, ctx2, 1); CPPUNIT_ASSERT_EQUAL((int)ZOK,rc); } + + void testSasl() { + int rc; + const char *saslopt = "-sasl"; + count = 0; + watchctx_t ctx1, ctx2; + + const char *serverin; + unsigned serverinlen; + const char *realm = "realm"; + const char *nonce = "nonce"; + + stopServer(); + startServerWithOpts(saslopt); + + // zoo_set_log_stream(stdout); + // zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG); + + zhandle_t *zk1 = createClient(&ctx1); + rc = zoo_sasl(zk1, NULL, (const char *) "", 0, &serverin, &serverinlen); + CPPUNIT_ASSERT_EQUAL((int) ZOK, rc); + // response should look like + // realm="zk-sasl-md5",nonce="4n7iytvP7E9GyRVvGQ8pATPPnXJ0GjOB5rmTzk3a",... + //LOG_DEBUG(("SASL Response: %s", serverin)); + CPPUNIT_ASSERT(strstr(serverin, realm)!=NULL); + CPPUNIT_ASSERT(strstr(serverin, nonce)!=NULL); + + zhandle_t *zk2 = createClient(&ctx2); + rc = zoo_asasl(zk2, NULL, (const char *) "", 0, saslDigestInitCompletion); + stopServer(); + startServer(); + + } }; volatile int Zookeeper_simpleSystem::count; diff --git a/zookeeper-client/zookeeper-client-c/tests/jaas.digest.server.conf b/zookeeper-client/zookeeper-client-c/tests/jaas.digest.server.conf new file mode 100644 index 00000000000..04cebbc3acd --- /dev/null +++ b/zookeeper-client/zookeeper-client-c/tests/jaas.digest.server.conf @@ -0,0 +1,4 @@ +Server { + org.apache.zookeeper.server.auth.DigestLoginModule required + user_super="test"; +}; diff --git a/zookeeper-client/zookeeper-client-c/tests/zkServer.sh b/zookeeper-client/zookeeper-client-c/tests/zkServer.sh index ebc3df42ba0..4a396338159 100755 --- a/zookeeper-client/zookeeper-client-c/tests/zkServer.sh +++ b/zookeeper-client/zookeeper-client-c/tests/zkServer.sh @@ -21,7 +21,7 @@ ZOOPORT=22181 if [ "x$1" == "x" ] then - echo "USAGE: $0 startClean|start|startReadOnly|startRequireSASLAuth|stop hostPorts" + echo "USAGE: $0 startClean|start|startReadOnly|startRequireSASLAuth|stop hostPorts [-sasl] [-verbose]" exit 2 fi @@ -110,6 +110,27 @@ fi PROPERTIES="-Dzookeeper.extendedTypesEnabled=true -Dznode.container.checkIntervalMs=100" +for var in "$@" +do + if [[ "x$var" != "x0" && "x$var" != "x1" && "x$var" != "x2" ]] + then + if [ "$var" == "-sasl" ] + then + SASLCONFFILE=tests/jaas.digest.server.conf + if [ "x${base_dir}" != "x" ] + then + SASLCONFFILE="${base_dir}/zookeeper-client/zookeeper-client-c/$SASLCONFFILE" + fi + PROPERTIES="$PROPERTIES -Dzookeeper.authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider" + PROPERTIES="$PROPERTIES -Djava.security.auth.login.config=$SASLCONFFILE" + fi + if [ "$var" == "-verbose" ] + then + PROPERTIES="$PROPERTIES -Dzookeeper.root.logger=DEBUG,CONSOLE -Dzookeeper.console.threshold=DEBUG" + fi + fi +done + case $1 in start|startClean) if [ "x${base_dir}" == "x" ]