From d0638f309b516b1c79f585561f75cebb272afc97 Mon Sep 17 00:00:00 2001 From: reshke Date: Tue, 1 Aug 2023 15:21:47 +0500 Subject: [PATCH] Cache auth info for auth query for 10 sec. (#523) * Cache auth info for auth query for 10 sec. * Invalidate auth cache on reload --- config-examples/odyssey-dev.conf | 183 ++----------------------------- sources/auth.c | 4 +- sources/auth_query.c | 79 ++++++++++++- sources/backend.c | 5 +- sources/client.h | 1 + sources/hashmap.c | 64 ++++++++++- sources/hashmap.h | 18 +++ sources/ldap.c | 3 +- sources/router.c | 4 +- sources/rules.c | 10 +- sources/storage.c | 10 ++ sources/storage.h | 9 ++ sources/system.c | 12 +- 13 files changed, 210 insertions(+), 192 deletions(-) diff --git a/config-examples/odyssey-dev.conf b/config-examples/odyssey-dev.conf index 53f4428f2..de98c1f86 100644 --- a/config-examples/odyssey-dev.conf +++ b/config-examples/odyssey-dev.conf @@ -12,10 +12,10 @@ log_syslog no log_syslog_ident "odyssey" log_syslog_facility "daemon" -log_debug yes +log_debug no log_config yes log_session yes -log_query yes +log_query no log_stats yes stats_interval 60 log_general_stats_prom yes @@ -52,79 +52,23 @@ storage "postgres_server" { type "remote" host "[localhost]:5432,localhost" port 5550 - target_session_attrs "read-only" - - watchdog { - authentication "none" - - storage "postgres_server" - storage_db "postgres" - storage_user "lag_usr" - - pool_routing "internal" - pool "transaction" - pool_size 10 - - pool_timeout 0 - pool_ttl 1201 - - pool_discard yes - pool_cancel yes - - server_lifetime 1901 - log_debug no - - watchdog_lag_query "SELECT TRUNC(EXTRACT(EPOCH FROM NOW())) - 100" - watchdog_lag_interval 10 - } -} - -database "db2" { - user "user1" { - authentication "none" - - storage "postgres_server" - - pool "session" - server_lifetime 3600 - - log_debug yes - pool_discard yes - - quantiles "0.99,0.95,0.5" - client_max 1007 - } -} - -database "db1" { - user "user1" { - authentication "none" - - storage "postgres_server" - - pool "transaction" - pool_ttl 10000 - server_lifetime 10000 - pool_size 10 - - log_debug yes - pool_discard no - pool_smart_discard yes - pool_reserve_prepared_statement yes - - quantiles "0.99,0.95,0.5" - client_max 1007 - } } database default { user default { - authentication "none" + authentication "md5" + + password "md588cb17a149f659b9a78ec4a33cbb3c7f" storage "postgres_server" - pool "session" + pool "transaction" pool_size 0 +# auth_query "SELECT usename, passwd FROM pg_shadow WHERE usename=$1" +# auth_query_db "postgres" +# auth_query_user "reshke" +# storage_password "reshke" + pool_timeout 0 pool_ttl 1201 @@ -149,111 +93,6 @@ database default { } } -database "postgres" { - user "user_aq_internal_pooling" { - authentication "none" - - storage "postgres_server" - - pool "session" - storage_user "reshke" - storage_db "postgres" - - log_debug yes - pool_discard yes - pool_routing "internal" - - quantiles "0.99,0.95,0.5" - client_max 107 - } - - - user "user_lag_pooling" { - authentication "none" - - storage "postgres_server" - - pool "session" - storage_user "reshke" - storage_db "postgres" - - log_debug yes - pool_discard yes - - catchup_timeout 10 - catchup_checks 1 - - quantiles "0.99,0.95,0.5" - client_max 107 - } - - user "userstmt" { - authentication "none" - - storage "postgres_server" - storage_password "1" - pool "statement" - pool_size 1 - - pool_timeout 0 - pool_ttl 60 - pool_discard no - pool_cancel yes - pool_rollback yes - - client_fwd_error yes - - application_name_add_host yes - reserve_session_server_connection no - server_lifetime 3600 - log_debug no - - quantiles "0.99,0.95,0.5" - client_max 107 - } - - user "useropt" { - authentication "none" - - storage "postgres_server" - storage_password "1" - pool "statement" - pool_size 1 - - pool_timeout 0 - pool_ttl 60 - pool_discard no - pool_cancel yes - pool_rollback yes - - client_fwd_error yes - - application_name_add_host yes - reserve_session_server_connection no - server_lifetime 3600 - log_debug no - - quantiles "0.99,0.95,0.5" - client_max 107 - -# add this options to backend-startup package - backend_startup_options { - "_pq_.service_auth_role" "odyssey" - } - -# this overrides anything passed by -# PGOPTIONS from client - options { - "statement_timeout" "0" - } - } - - user default { - authentication "none" - storage "postgres_server" - pool "session" - } -} storage "local" { type "local" } diff --git a/sources/auth.c b/sources/auth.c index 80b1b968f..d92ad71b8 100644 --- a/sources/auth.c +++ b/sources/auth.c @@ -1032,8 +1032,8 @@ int od_auth_backend(od_server_t *server, machine_msg_t *msg, return -1; } - od_debug(&instance->logger, "auth", NULL, server, - "recieved msg type %u", auth_type); + od_debug(&instance->logger, "auth", NULL, server, + "recieved msg type %u", auth_type); msg = NULL; diff --git a/sources/auth_query.c b/sources/auth_query.c index d223e2bf4..c45756a99 100644 --- a/sources/auth_query.c +++ b/sources/auth_query.c @@ -97,11 +97,55 @@ int od_auth_query(od_client_t *client, char *peer) { od_global_t *global = client->global; od_rule_t *rule = client->rule; + od_rule_storage_t *storage = rule->storage; kiwi_var_t *user = &client->startup.user; kiwi_password_t *password = &client->password; od_instance_t *instance = global->instance; od_router_t *router = global->router; + /* check odyssey storage auh query cache before + * doing any actual work + */ + /* username -> password cache */ + od_hashmap_elt_t *value; + od_hashmap_elt_t key; + od_auth_cache_value_t *cache_value; + od_hash_t keyhash; + uint64_t current_time; + + key.data = user->name; + key.len = user->name_len; + + keyhash = od_murmur_hash(key.data, key.len); + /* acquire hash map entry lock */ + value = od_hashmap_lock_key(storage->acache, keyhash, &key); + + if (value->data == NULL) { + /* one-time initialize */ + value->data = malloc(sizeof(od_auth_cache_value_t)); + value->len = sizeof(od_auth_cache_value_t); + } + + cache_value = (od_auth_cache_value_t *)value->data; + + current_time = machine_time_us(); + + if (cache_value != NULL + /* password cached for 10 sec */ + && current_time - cache_value->timestamp < 10 * interval_usec) { + od_debug(&instance->logger, "auth_query", NULL, NULL, + "reusing cached password for user %.*s", + user->name_len, user->name); + /* unlock hashmap entry */ + password->password_len = cache_value->passwd_len; + password->password = malloc(password->password_len + 1); + strncpy(password->password, cache_value->passwd, + cache_value->passwd_len); + password->password[password->password_len] = '\0'; + od_hashmap_unlock_key(storage->acache, keyhash, &key); + return OK_RESPONSE; + } + /* create internal auth client */ od_client_t *auth_client; @@ -110,9 +154,13 @@ int od_auth_query(od_client_t *client, char *peer) if (auth_client == NULL) { od_debug(&instance->logger, "auth_query", auth_client, NULL, "failed to allocate internal auth query client"); - return NOT_OK_RESPONSE; + goto error; } + od_debug(&instance->logger, "auth_query", auth_client, NULL, + "acquiring password for user %.*s", user->name_len, + user->name); + /* set auth query route user and database */ kiwi_var_set(&auth_client->startup.user, KIWI_VAR_UNDEF, rule->auth_query_user, strlen(rule->auth_query_user) + 1); @@ -128,7 +176,7 @@ int od_auth_query(od_client_t *client, char *peer) "failed to route internal auth query client: %s", od_router_status_to_str(status)); od_client_free(auth_client); - return NOT_OK_RESPONSE; + goto error; } /* attach */ @@ -140,7 +188,7 @@ int od_auth_query(od_client_t *client, char *peer) od_router_status_to_str(status)); od_router_unroute(router, auth_client); od_client_free(auth_client); - return NOT_OK_RESPONSE; + goto error; } od_server_t *server; server = auth_client->server; @@ -152,6 +200,7 @@ int od_auth_query(od_client_t *client, char *peer) /* connect to server, if necessary */ int rc; if (server->io.io == NULL) { + /* acquire new backend connection for auth query */ rc = od_backend_connect(server, "auth_query", NULL, auth_client); if (rc == NOT_OK_RESPONSE) { @@ -162,7 +211,7 @@ int od_auth_query(od_client_t *client, char *peer) od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free(auth_client); - return NOT_OK_RESPONSE; + goto error; } } @@ -181,7 +230,7 @@ int od_auth_query(od_client_t *client, char *peer) od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free(auth_client); - return NOT_OK_RESPONSE; + goto error; } if (od_auth_parse_passwd_from_datarow(&instance->logger, msg, password) == NOT_OK_RESPONSE) { @@ -190,12 +239,30 @@ int od_auth_query(od_client_t *client, char *peer) od_router_close(router, auth_client); od_router_unroute(router, auth_client); od_client_free(auth_client); - return NOT_OK_RESPONSE; + goto error; + } + + /* save received password and recieve timestamp */ + if (cache_value->passwd != NULL) { + /* drop previous value */ + free(cache_value->passwd); } + cache_value->passwd_len = password->password_len; + cache_value->passwd = malloc(password->password_len); + strncpy(cache_value->passwd, password->password, + cache_value->passwd_len); + + cache_value->timestamp = current_time; /* detach and unroute */ od_router_detach(router, auth_client); od_router_unroute(router, auth_client); od_client_free(auth_client); + od_hashmap_unlock_key(storage->acache, keyhash, &key); return OK_RESPONSE; + +error: + /* unlock hashmap entry */ + od_hashmap_unlock_key(storage->acache, keyhash, &key); + return NOT_OK_RESPONSE; } diff --git a/sources/backend.c b/sources/backend.c index 4b1a7197b..bd7236044 100644 --- a/sources/backend.c +++ b/sources/backend.c @@ -125,10 +125,9 @@ static inline int od_backend_startup(od_server_t *server, { NULL, 0 } }; - od_debug(&instance->logger, "startup", NULL, server, - "startup server connection with user %s & database %s", route->id.user, route->id.database); - + "startup server connection with user %s & database %s", + route->id.user, route->id.database); for (size_t i = 0; i < route->rule->backend_startup_vars_sz; i++) { argv[i << 1].name = route->rule->backend_startup_vars[i].name; diff --git a/sources/client.h b/sources/client.h index 4f6df39df..19ec4ed1f 100644 --- a/sources/client.h +++ b/sources/client.h @@ -140,6 +140,7 @@ static inline void od_client_free(od_client_t *client) od_io_free(&client->io); if (client->cond) machine_cond_free(client->cond); + /* clear password if saved any */ kiwi_password_free(&client->password); kiwi_password_free(&client->received_password); if (client->prep_stmt_ids) { diff --git a/sources/hashmap.c b/sources/hashmap.c index 47756f3c0..60c633ce1 100644 --- a/sources/hashmap.c +++ b/sources/hashmap.c @@ -31,7 +31,9 @@ od_retcode_t od_hashmap_list_item_free(od_hashmap_list_item_t *l) { od_list_unlink(&l->link); free(l->key.data); - free(l->value.data); + if (l->value.data) { + free(l->value.data); + } free(l); return OK_RESPONSE; @@ -109,6 +111,26 @@ od_retcode_t od_hashmap_free(od_hashmap_t *hm) return OK_RESPONSE; } +od_retcode_t od_hashmap_empty(od_hashmap_t *hm) +{ + for (size_t i = 0; i < hm->size; ++i) { + pthread_mutex_lock(&hm->buckets[i]->mu); + + od_list_t *j, *n; + + od_list_foreach_safe(&hm->buckets[i]->nodes->link, j, n) + { + od_hashmap_list_item_t *it; + it = od_container_of(j, od_hashmap_list_item_t, link); + od_hashmap_list_item_free(it); + } + + pthread_mutex_unlock(&hm->buckets[i]->mu); + } + + return OK_RESPONSE; +} + static inline od_hashmap_elt_t *od_bucket_search(od_hashmap_bucket_t *b, void *value, size_t value_len) { @@ -161,8 +183,14 @@ int od_hashmap_insert(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_list_item_add( hm->buckets[bucket_index]->nodes, it); ret = 0; + } else { + /* oom or other error */ + return -1; } } else { + /* element alrady exists, + * copy *value content to ptr data + * free previous value */ free(ptr->data); od_hashmap_elt_copy(ptr, *value); *value = ptr; @@ -184,3 +212,37 @@ od_hashmap_elt_t *od_hashmap_find(od_hashmap_t *hm, od_hash_t keyhash, pthread_mutex_unlock(&hm->buckets[bucket_index]->mu); return ptr; } + +od_hashmap_elt_t *od_hashmap_lock_key(od_hashmap_t *hm, od_hash_t keyhash, + od_hashmap_elt_t *key) +{ + size_t bucket_index = keyhash % hm->size; + pthread_mutex_lock(&hm->buckets[bucket_index]->mu); + + od_hashmap_elt_t *ptr = od_bucket_search(hm->buckets[bucket_index], + key->data, key->len); + if (ptr == NULL) { + od_hashmap_list_item_t *it; + it = od_hashmap_list_item_create(); + if (it != NULL) { + od_hashmap_elt_copy(&it->key, key); + od_hashmap_list_item_add( + hm->buckets[bucket_index]->nodes, it); + return &it->value; + } else { + /* oom or other error */ + return NULL; + } + } else { + /* element alrady exists, simpty return locked key */ + return ptr; + } +} + +int od_hashmap_unlock_key(od_hashmap_t *hm, od_hash_t keyhash, + od_hashmap_elt_t *key) +{ + size_t bucket_index = keyhash % hm->size; + pthread_mutex_unlock(&hm->buckets[bucket_index]->mu); + return 0 /* OK */; +} diff --git a/sources/hashmap.h b/sources/hashmap.h index 855064711..2ab6776e3 100644 --- a/sources/hashmap.h +++ b/sources/hashmap.h @@ -43,7 +43,25 @@ extern od_hashmap_t *od_hashmap_create(size_t sz); extern od_retcode_t od_hashmap_free(od_hashmap_t *hm); od_hashmap_elt_t *od_hashmap_find(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key); + +/* This function insert new key into hashmap +* If hashmap already contains value assotiated with key, +* it will be rewritten. +*/ int od_hashmap_insert(od_hashmap_t *hm, od_hash_t keyhash, od_hashmap_elt_t *key, od_hashmap_elt_t **value); +/* LOCK-UNLOCK API */ +/* given key and its +* keyhash (murmurhash etc) return poitner +* to hashmap mutex-locked value pointer */ +od_hashmap_elt_t *od_hashmap_lock_key(od_hashmap_t *hm, od_hash_t keyhash, + od_hashmap_elt_t *key); + +int od_hashmap_unlock_key(od_hashmap_t *hm, od_hash_t keyhash, + od_hashmap_elt_t *key); + +/* clear hashmap */ +od_retcode_t od_hashmap_empty(od_hashmap_t *hm); + #endif /* OD_HASHMAP_H */ diff --git a/sources/ldap.c b/sources/ldap.c index afe7be222..0129b0668 100644 --- a/sources/ldap.c +++ b/sources/ldap.c @@ -195,7 +195,8 @@ od_retcode_t od_ldap_server_prepare(od_logger_t *logger, od_ldap_server_t *serv, &search_message); od_debug(logger, "auth_ldap", client, NULL, - "basedn search entries with filter: %s and attrib %s ", filter, attributes[0]); + "basedn search entries with filter: %s and attrib %s ", + filter, attributes[0]); if (rc != LDAP_SUCCESS) { od_error(logger, "auth_ldap", client, NULL, diff --git a/sources/router.c b/sources/router.c index 90504d162..77c4d0356 100644 --- a/sources/router.c +++ b/sources/router.c @@ -367,9 +367,9 @@ od_router_status_t od_router_route(od_router_t *router, od_client_t *client) "matching rule: %s %s with %s routing type to %s client", rule->db_name, rule->user_name, rule->pool->routing_type == NULL ? "client visible" : - rule->pool->routing_type, + rule->pool->routing_type, client->type == OD_POOL_CLIENT_INTERNAL ? "internal" : - "external"); + "external"); if (!od_rule_matches_client(rule->pool, client->type)) { // emulate not found error od_router_unlock(router); diff --git a/sources/rules.c b/sources/rules.c index 97be093d0..7bd106590 100644 --- a/sources/rules.c +++ b/sources/rules.c @@ -584,6 +584,8 @@ __attribute__((hot)) int od_rules_merge(od_rules_t *rules, od_rules_t *src, rule = od_container_of(i, od_rule_t, link); rule->mark = 1; count_mark++; + + od_hashmap_empty(rule->storage->acache); } /* select dropped rules */ @@ -882,14 +884,14 @@ int od_rules_autogenerate_defaults(od_rules_t *rules, od_logger_t *logger) rule->pool->size = OD_DEFAULT_INTERNAL_POLL_SZ; rule->enable_password_passthrough = true; rule->storage = od_rules_storage_copy(default_rule->storage); - - rule->storage_password = strdup(default_rule->storage_password); - rule->storage_password_len = default_rule->storage_password_len; - if (!rule->storage) { // oom return NOT_OK_RESPONSE; } + + rule->storage_password = strdup(default_rule->storage_password); + rule->storage_password_len = default_rule->storage_password_len; + od_log(logger, "config", NULL, NULL, "default internal rule auto-generated"); return OK_RESPONSE; diff --git a/sources/storage.c b/sources/storage.c index 5d056a3b4..ea0f2e4c8 100644 --- a/sources/storage.c +++ b/sources/storage.c @@ -74,6 +74,10 @@ od_rule_storage_t *od_rules_storage_allocate(void) storage->target_session_attrs = OD_TARGET_SESSION_ATTRS_ANY; storage->rr_counter = 0; +#define OD_STORAGE_DEFAULT_HASHMAP_SZ 420u + + storage->acache = od_hashmap_create(OD_STORAGE_DEFAULT_HASHMAP_SZ); + od_list_init(&storage->link); return storage; } @@ -103,6 +107,10 @@ void od_rules_storage_free(od_rule_storage_t *storage) free(storage->endpoints); } + if (storage->acache) { + od_hashmap_free(storage->acache); + } + od_list_unlink(&storage->link); free(storage); } @@ -176,6 +184,8 @@ od_rule_storage_t *od_rules_storage_copy(od_rule_storage_t *storage) } } + /* storage auth cache not copied */ + copy->target_session_attrs = storage->target_session_attrs; return copy; diff --git a/sources/storage.h b/sources/storage.h index d1100052d..677186a45 100644 --- a/sources/storage.h +++ b/sources/storage.h @@ -51,6 +51,13 @@ typedef enum { OD_TARGET_SESSION_ATTRS_ANY, } od_target_session_attrs_t; +typedef struct od_auth_cache_value od_auth_cache_value_t; +struct od_auth_cache_value { + uint64_t timestamp; + char *passwd; + uint32_t passwd_len; +}; + struct od_rule_storage { od_tls_opts_t *tls_opts; @@ -71,6 +78,8 @@ struct od_rule_storage { int server_max_routing; od_storage_watchdog_t *watchdog; + od_hashmap_t *acache; + od_list_t link; }; diff --git a/sources/system.c b/sources/system.c index d5450d308..8c5cedec6 100644 --- a/sources/system.c +++ b/sources/system.c @@ -389,6 +389,7 @@ void od_system_config_reload(od_system_t *system) od_router_t *router = system->global->router; od_extention_t *extentions = system->global->extentions; od_hba_t *hba = system->global->hba; + od_list_t *i; od_log(&instance->logger, "config", NULL, NULL, "importing changes from '%s'", instance->config_file); @@ -440,10 +441,19 @@ void od_system_config_reload(od_system_t *system) od_config_reload(&instance->config, &config); od_hba_reload(hba, &hba_rules); + /* auto-generate default rule for auth_query if none specified */ + rc = od_rules_autogenerate_defaults(&rules, &instance->logger); + + if (rc == -1) { + pthread_mutex_unlock(&router->rules.mu); + od_config_free(&config); + od_rules_free(&rules); + return; + } + pthread_mutex_unlock(&router->rules.mu); /* Reload TLS certificates */ - od_list_t *i; od_list_foreach(&router->servers, i) { od_system_server_t *server;