From c85b90e860bdb4b5a1fb52413ddd8b1731dfc476 Mon Sep 17 00:00:00 2001 From: eugene Date: Wed, 19 Jun 2024 13:47:04 -0400 Subject: [PATCH 1/4] minor MFA clean up and fixes add MFA tester program --- includes/ziti/ziti.h | 4 +- library/auth_queries.c | 4 +- library/legacy_auth.c | 8 +- library/ziti.c | 2 + library/ziti_ctrl.c | 2 +- programs/CMakeLists.txt | 3 + programs/mfa_tester/CMakeLists.txt | 5 + programs/mfa_tester/ziti_mfa.cpp | 301 +++++++++++++++++++++++++++++ vcpkg.json | 3 + 9 files changed, 322 insertions(+), 10 deletions(-) create mode 100644 programs/mfa_tester/CMakeLists.txt create mode 100644 programs/mfa_tester/ziti_mfa.cpp diff --git a/includes/ziti/ziti.h b/includes/ziti/ziti.h index e4086508..af9b0fe8 100644 --- a/includes/ziti/ziti.h +++ b/includes/ziti/ziti.h @@ -883,7 +883,7 @@ extern void ziti_mfa_enroll(ziti_context ztx, ziti_mfa_enroll_cb enroll_cb, void * @param ctx additional context to be passed into the remove_cb callback */ ZITI_FUNC -extern void ziti_mfa_remove(ziti_context ztx, char *code, ziti_mfa_cb remove_cb, void *ctx); +extern void ziti_mfa_remove(ziti_context ztx, const char *code, ziti_mfa_cb remove_cb, void *ctx); /** * @brief Attempts to verify MFA enrollment @@ -919,7 +919,7 @@ extern void ziti_mfa_verify(ziti_context ztx, char *code, ziti_mfa_cb verify_cb, * @param ctx additional context to be passed into the get_cb callback */ ZITI_FUNC -extern void ziti_mfa_get_recovery_codes(ziti_context ztx, char *code, ziti_mfa_recovery_codes_cb get_cb, void *ctx); +extern void ziti_mfa_get_recovery_codes(ziti_context ztx, const char *code, ziti_mfa_recovery_codes_cb get_cb, void *ctx); /** * @brief Attempts to generate new recovery codes and retrieve the new recovery codes for MFA diff --git a/library/auth_queries.c b/library/auth_queries.c index e86d9f2d..45b4584f 100644 --- a/library/auth_queries.c +++ b/library/auth_queries.c @@ -190,7 +190,7 @@ void ziti_mfa_remove_internal_cb(void *empty, const ziti_error *err, void *ctx) FREE(ctx); } -void ziti_mfa_remove(ziti_context ztx, char *code, ziti_mfa_cb remove_cb, void *ctx) { +void ziti_mfa_remove(ziti_context ztx, const char *code, ziti_mfa_cb remove_cb, void *ctx) { if (!ztx->enabled) { remove_cb(ztx, ZITI_DISABLED, ctx); return; @@ -250,7 +250,7 @@ void ziti_mfa_get_recovery_codes_internal_cb(ziti_mfa_recovery_codes *rc, const FREE(ctx); } -void ziti_mfa_get_recovery_codes(ziti_context ztx, char *code, ziti_mfa_recovery_codes_cb get_cb, void *ctx) { +void ziti_mfa_get_recovery_codes(ziti_context ztx, const char *code, ziti_mfa_recovery_codes_cb get_cb, void *ctx) { if (!ztx->enabled) { get_cb(ztx, ZITI_DISABLED, NULL, ctx); return; diff --git a/library/legacy_auth.c b/library/legacy_auth.c index b30ce2c7..04bb3c6d 100644 --- a/library/legacy_auth.c +++ b/library/legacy_auth.c @@ -224,16 +224,14 @@ void auth_timer_cb(uv_timer_t *t) { } static ziti_auth_query_mfa* get_mfa(ziti_api_session *session) { - ziti_auth_query_mfa *aq, *ziti_mfa = NULL; - + ziti_auth_query_mfa *aq; MODEL_LIST_FOREACH(aq, session->auth_queries) { if (strcmp(aq->type_id, AUTH_QUERY_TYPE_MFA) == 0 && strcmp(aq->provider, MFA_PROVIDER_ZITI) == 0) { - ziti_mfa = aq; - break; + return aq; } } - return ziti_mfa; + return NULL; } static uint64_t refresh_delay(ziti_api_session *session) { diff --git a/library/ziti.c b/library/ziti.c index 7f7f9b88..525704fd 100644 --- a/library/ziti.c +++ b/library/ziti.c @@ -214,6 +214,8 @@ void ziti_set_impossible_to_authenticate(ziti_context ztx) { void ziti_set_partially_authenticated(ziti_context ztx, const ziti_auth_query_mfa *mfa_q) { ZTX_LOG(DEBUG, "setting api_session_state[%d] to %d", ztx->auth_state, ZitiAuthStatePartiallyAuthenticated); + update_ctrl_status(ztx, ZITI_PARTIALLY_AUTHENTICATED, NULL); + ziti_event_t ev = { .type = ZitiMfaAuthEvent, .mfa_auth_event = { diff --git a/library/ziti_ctrl.c b/library/ziti_ctrl.c index 81710fde..ccdde8ea 100644 --- a/library/ziti_ctrl.c +++ b/library/ziti_ctrl.c @@ -288,7 +288,7 @@ static void internal_ctrl_list_cb(ziti_controller_detail_array arr, const ziti_e model_map_clear(&old, (void (*)(void *)) free_ziti_controller_detail_ptr); free(arr); - if (change) { + if (change && ctrl->is_ha) { ctrl->change_cb(ctrl->cb_ctx, &ctrl->endpoints); } } diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 323bc614..4d24ff6a 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -1,4 +1,5 @@ +find_package(CLI11 CONFIG REQUIRED) add_subdirectory(ziti-prox-c) add_subdirectory(host-proxy) @@ -12,3 +13,5 @@ add_subdirectory(wzcat) add_subdirectory(sample-bridge) add_subdirectory(zitilib-samples) + +add_subdirectory(mfa_tester) diff --git a/programs/mfa_tester/CMakeLists.txt b/programs/mfa_tester/CMakeLists.txt new file mode 100644 index 00000000..71cb08ba --- /dev/null +++ b/programs/mfa_tester/CMakeLists.txt @@ -0,0 +1,5 @@ +add_executable(ziti_mfa ziti_mfa.cpp) +target_link_libraries(ziti_mfa + PUBLIC ziti + PRIVATE CLI11::CLI11 +) diff --git a/programs/mfa_tester/ziti_mfa.cpp b/programs/mfa_tester/ziti_mfa.cpp new file mode 100644 index 00000000..99b8b774 --- /dev/null +++ b/programs/mfa_tester/ziti_mfa.cpp @@ -0,0 +1,301 @@ +/* + Copyright 2024 NetFoundry Inc. + + Licensed 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 + + https://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 + +#include +#include + +#include "ziti/ziti.h" +#include +#include "ziti/ziti_log.h" + +typedef void(*prompt_cb)(ziti_context, const char *input); +static std::string identity; +static uv_loop_t *loop = uv_default_loop(); + +struct prompt_req { + uv_work_t req{}; + std::string prompt; + std::string input; + prompt_cb cb{}; + std::mutex lock; +}; + +static void prompt_work(uv_work_t *req) { + auto pr = (prompt_req *) req; + std::cout << pr->prompt << ": "; + std::getline(std::cin, pr->input); +} + +static void prompt_done(uv_work_t *req, int status) { + auto pr = (prompt_req *) req; + auto input = pr->input; + auto cb = pr->cb; + + pr->lock.unlock(); + + cb((ziti_context) req->data, input.c_str()); +} + + +static void ztx_prompt(ziti_context ztx, const std::string &prompt, void(*cb)(ziti_context, const char *input)) { + static prompt_req req; + auto lock = req.lock.try_lock(); + assert(lock); + + req.prompt = prompt; + req.input.clear(); + req.cb = cb; + req.req.data = ztx; + uv_queue_work(loop, (uv_work_t *) &req, prompt_work, prompt_done); +} + + +#define CHECK(op) do { \ +auto rc = op; \ +if (rc != ZITI_OK) \ +std::cerr << "ERROR: " << rc << "/" << ziti_errorstr(rc) << std::endl; \ +} while(0) + +static void on_enroll(ziti_context ztx, int status, ziti_mfa_enrollment *info, void *data) { + if (info) { + printf("\nMFA enrollment: \n" + "verified: %d\n" + "url: %s\n", info->is_verified, info->provisioning_url); + + if (!info->is_verified) { + ztx_prompt(ztx, "verify", [](ziti_context ztx, const char *code) { + ziti_mfa_verify(ztx, (char *) code, [](ziti_context ztx, int status, void *) { + if (status == ZITI_OK) { + printf("MFA Verify success!\n"); + } else { + fprintf(stderr, "failed to verify: %d/%s\n", status, ziti_errorstr(status)); + } + ziti_shutdown(ztx); + }, nullptr); + }); + } + } else { + std::cerr << "enroll status: " << status << "/" << ziti_errorstr(status) << std::endl; + } +} + + +static void base_run(void(*handler)(ziti_context, const ziti_event_t *)) { + ziti_config config = {nullptr}; + ziti_context ztx = nullptr; + + ziti_options opts = { + .app_ctx = (void *) (handler), + .events = ZitiContextEvent | ZitiMfaAuthEvent, + .event_cb = handler, + }; + std::cerr << std::endl; + + CHECK(ziti_load_config(&config, identity.c_str())); + CHECK(ziti_context_init(&ztx, &config)); + + CHECK(ziti_context_set_options(ztx, &opts)); + CHECK(ziti_context_run(ztx, loop)); + + std::cout << "starting event loop" << std::endl; + uv_run(loop, UV_RUN_DEFAULT); + +} + +static void get_codes() { + base_run([](ziti_context ztx, const ziti_event_t *ev) { + switch (ev->type) { + case ZitiContextEvent: { + const ziti_context_event &e = ev->ctx; + if (e.ctrl_status == ZITI_PARTIALLY_AUTHENTICATED) { + std::cout << "enrolled in MFA" << std::endl; + } else if (e.ctrl_status == ZITI_OK) { + std::cout << "auth SUCCESS" << std::endl; + } else { + std::cout << e.err << std::endl; + } + break; + } + case ZitiMfaAuthEvent: { + const ziti_mfa_auth_event &e = ev->mfa_auth_event; + std::string prompt = std::string("enter ") + e.auth_query_mfa->type_id + "/" + + e.auth_query_mfa->provider + " code"; + ztx_prompt(ztx, prompt, [](ziti_context z, const char *code) { + ziti_mfa_auth(z, code, [](ziti_context z, int status, void *) { + if (status == ZITI_OK) { + std::cout << "MFA auth success!" << std::endl; + ztx_prompt(z, "enter MFA code", [](ziti_context z, const char *code) { + ziti_mfa_get_recovery_codes(z, code, [](ziti_context z, int status, char **codes, void *) { + if (status == ZITI_OK) { + for(int i = 0; codes && codes[i]; i++) { + std::cout << codes[i] << std::endl; + } + } else { + std::cout << "MFA auth failed: " << status << '/' << ziti_errorstr(status) << std::endl; + } + ziti_shutdown(z); + }, nullptr); + }); + + } else { + std::cout << "MFA auth failed: " << status << '/' << ziti_errorstr(status) << std::endl; + } + }, nullptr); + }); + + break; + } + default: + std::cout << "unhandled event" << std::endl; + } + }); +} + +static void test_mfa() { + base_run([](ziti_context ztx, const ziti_event_t *ev) { + switch (ev->type) { + case ZitiContextEvent: { + const ziti_context_event &e = ev->ctx; + if (e.ctrl_status == ZITI_PARTIALLY_AUTHENTICATED) { + std::cout << "enrolled in MFA" << std::endl; + } else if (e.ctrl_status == ZITI_OK) { + std::cout << "auth SUCCESS" << std::endl; + ziti_shutdown(ztx); + } else { + std::cout << e.err << std::endl; + } + break; + } + case ZitiMfaAuthEvent: { + const ziti_mfa_auth_event &e = ev->mfa_auth_event; + std::string prompt = std::string("enter ") + e.auth_query_mfa->type_id + "/" + + e.auth_query_mfa->provider + " code"; + ztx_prompt(ztx, prompt, [](ziti_context z, const char *code) { + ziti_mfa_auth(z, code, [](ziti_context z, int status, void *) { + if (status == ZITI_OK) { + std::cout << "MFA auth success!" << std::endl; + } else { + std::cout << "MFA auth failed: " << status << '/' << ziti_errorstr(status) << std::endl; + } + }, nullptr); + }); + break; + } + default: + std::cout << "unhandled event" << std::endl; + } + + }); +} + +static void enroll_mfa() { + std::cout << "enrolling identity: " << identity << std::endl; + base_run([](ziti_context ztx, const ziti_event_t *ev) { + switch (ev->type) { + case ZitiContextEvent: { + const ziti_context_event &e = ev->ctx; + if (e.ctrl_status == ZITI_PARTIALLY_AUTHENTICATED) { + std::cout << "already enrolled in MFA" << std::endl; + } else if (e.ctrl_status == ZITI_OK) { + ztx_prompt(ztx, "are you sure[y/N]?", [](ziti_context z, const char *resp) { + if (tolower(resp[0]) == 'y') { + ziti_mfa_enroll(z, on_enroll, nullptr); + } + }); + } else { + std::cout << e.err << std::endl; + } + break; + } + case ZitiMfaAuthEvent: { + const ziti_mfa_auth_event &e = ev->mfa_auth_event; + std::cout << "details: " << e.auth_query_mfa->type_id << "/" + << (e.auth_query_mfa->provider) << std::endl; + ziti_shutdown(ztx); + break; + } + default: + std::cout << "unhandled event" << std::endl; + } + }); +} + +static void delete_mfa() { + base_run([](ziti_context ztx, const ziti_event_t *ev){ + switch (ev->type) { + case ZitiContextEvent: { + const ziti_context_event &e = ev->ctx; + if (e.ctrl_status == ZITI_PARTIALLY_AUTHENTICATED) { + std::cout << "enrolled in MFA" << std::endl; + } else if (e.ctrl_status == ZITI_OK) { + std::cout << "auth SUCCESS" << std::endl; + ztx_prompt(ztx, "enter MFA code to remove", [](ziti_context z, const char *code){ + ziti_mfa_remove(z, code, [](ziti_context z, int status, void *ctx){ + std::cout << "remove status: " << ziti_errorstr(status) << std::endl; + ziti_shutdown(z); + }, nullptr); + }); + } else { + std::cout << e.err << std::endl; + } + break; + } + case ZitiMfaAuthEvent: { + const ziti_mfa_auth_event &e = ev->mfa_auth_event; + std::string prompt = std::string("enter ") + e.auth_query_mfa->type_id + "/" + + e.auth_query_mfa->provider + " code"; + ztx_prompt(ztx, prompt, [](ziti_context z, const char *code) { + ziti_mfa_auth(z, code, [](ziti_context z, int status, void *) { + if (status == ZITI_OK) { + std::cout << "MFA auth success!" << std::endl; + } else { + std::cout << "MFA auth failed: " << status << '/' << ziti_errorstr(status) << std::endl; + } + }, nullptr); + }); + break; + } + default: + std::cout << "unhandled event" << std::endl; + } + }); +} + +std::ofstream logfile("/tmp/ziti_mfa", std::ios::ate); +const char *lbl[] = { + "E", "W", "I", "D", "V", "T", +}; + +static void logger(int level, const char *loc, const char *msg, size_t msglen) { + logfile << lbl[level] << ": " << loc << " " << std::string(msg, msglen) << std::endl; +} + +int main(int argc, char *argv[]) { + CLI::App app("ziti MFA test program", "ziti_mfa"); + + ziti_log_init(loop, 3, logger); + app.add_option("-i,--identity", identity, "ziti identity")->required()->check(CLI::ExistingFile); + + app.add_subcommand("enroll", "enroll identity to MFA")->final_callback(enroll_mfa); + app.add_subcommand("test", "test MFA code auth")->final_callback(test_mfa); + app.add_subcommand("codes", "get recovery codes")->final_callback(get_codes); + app.add_subcommand("delete", "remove MFA enrollment")->final_callback(delete_mfa); + + app.require_subcommand(1); + CLI11_PARSE(app, argc, argv); +} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index cdd98613..0bafa3a6 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,6 +26,9 @@ { "name": "getopt-win32", "platform": "windows" + }, + { + "name": "cli11" } ] } From 1b49562155876ebeab6f478147daed2ee2b18464 Mon Sep 17 00:00:00 2001 From: eugene Date: Tue, 9 Jul 2024 11:06:48 -0400 Subject: [PATCH 2/4] include for std::mutex --- programs/mfa_tester/ziti_mfa.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/programs/mfa_tester/ziti_mfa.cpp b/programs/mfa_tester/ziti_mfa.cpp index 99b8b774..67a6f16e 100644 --- a/programs/mfa_tester/ziti_mfa.cpp +++ b/programs/mfa_tester/ziti_mfa.cpp @@ -18,9 +18,11 @@ #include #include +#include -#include "ziti/ziti.h" #include + +#include "ziti/ziti.h" #include "ziti/ziti_log.h" typedef void(*prompt_cb)(ziti_context, const char *input); From 0828e5cd68c8a403775c3dc836d1761fd0e61d20 Mon Sep 17 00:00:00 2001 From: eugene Date: Tue, 9 Jul 2024 11:25:49 -0400 Subject: [PATCH 3/4] include for std::mutex --- programs/mfa_tester/ziti_mfa.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/programs/mfa_tester/ziti_mfa.cpp b/programs/mfa_tester/ziti_mfa.cpp index 67a6f16e..270598ef 100644 --- a/programs/mfa_tester/ziti_mfa.cpp +++ b/programs/mfa_tester/ziti_mfa.cpp @@ -54,16 +54,19 @@ static void prompt_done(uv_work_t *req, int status) { } -static void ztx_prompt(ziti_context ztx, const std::string &prompt, void(*cb)(ziti_context, const char *input)) { +static bool ztx_prompt(ziti_context ztx, const std::string &prompt, void(*cb)(ziti_context, const char *input)) { static prompt_req req; - auto lock = req.lock.try_lock(); - assert(lock); - - req.prompt = prompt; - req.input.clear(); - req.cb = cb; - req.req.data = ztx; - uv_queue_work(loop, (uv_work_t *) &req, prompt_work, prompt_done); + auto locked = req.lock.try_lock(); + + if (locked) { + req.prompt = prompt; + req.input.clear(); + req.cb = cb; + req.req.data = ztx; + uv_queue_work(loop, (uv_work_t *) &req, prompt_work, prompt_done); + } + + return locked; } From a90566c2491b10d6930298fea8c754e4ef7e821d Mon Sep 17 00:00:00 2001 From: eugene Date: Tue, 9 Jul 2024 11:38:48 -0400 Subject: [PATCH 4/4] use C++20 for windows --- programs/mfa_tester/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/programs/mfa_tester/CMakeLists.txt b/programs/mfa_tester/CMakeLists.txt index 71cb08ba..3c97af61 100644 --- a/programs/mfa_tester/CMakeLists.txt +++ b/programs/mfa_tester/CMakeLists.txt @@ -1,4 +1,8 @@ add_executable(ziti_mfa ziti_mfa.cpp) +set_target_properties(ziti_mfa PROPERTIES + CXX_STANDARD 20 +) + target_link_libraries(ziti_mfa PUBLIC ziti PRIVATE CLI11::CLI11