From 815a7cefd15b2928f3a97c7be9eef2ebee688351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= Date: Tue, 24 Jan 2012 13:42:59 +0100 Subject: [PATCH] SUDO Integration - in-memory cache in responder New sudo responder option: cache_timeout https://fedorahosted.org/sssd/ticket/1111 --- Makefile.am | 1 + src/confdb/confdb.h | 2 + src/man/sssd.conf.5.xml | 38 ++++ src/responder/sudo/sudosrv.c | 21 ++ src/responder/sudo/sudosrv_cache.c | 284 ++++++++++++++++++++++++++++ src/responder/sudo/sudosrv_cmd.c | 46 ++++- src/responder/sudo/sudosrv_get_sudorules.c | 25 ++- src/responder/sudo/sudosrv_private.h | 33 ++++ 8 files changed, 441 insertions(+), 9 deletions(-) create mode 100644 src/responder/sudo/sudosrv_cache.c diff --git a/Makefile.am b/Makefile.am index 710f33c67c28ee06896dd9edbb71aa8cc8672c4b..409dc84d7fc100039804b1272e3a777d607f7b03 100644 --- a/Makefile.am +++ b/Makefile.am @@ -469,6 +469,7 @@ sssd_sudo_SOURCES = \ src/responder/sudo/sudosrv_get_sudorules.c \ src/responder/sudo/sudosrv_query.c \ src/responder/sudo/sudosrv_dp.c \ + src/responder/sudo/sudosrv_cache.c \ $(SSSD_RESPONDER_OBJ) sssd_sudo_LDADD = \ $(SSSD_LIBS) \ diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h index 7cfc73d2b8a9dd61c796fcc14d69c778dc2a0227..83da4474402568ae7f5f9eab61fd3d5dcfc0e773 100644 --- a/src/confdb/confdb.h +++ b/src/confdb/confdb.h @@ -93,6 +93,8 @@ /* SUDO */ #define CONFDB_SUDO_CONF_ENTRY "config/sudo" +#define CONFDB_SUDO_CACHE_TIMEOUT "sudo_cache_timeout" +#define CONFDB_DEFAULT_SUDO_CACHE_TIMEOUT 180 /* Data Provider */ #define CONFDB_DP_CONF_ENTRY "config/dp" diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml index fee40a6a14ae6a9ea7282a1f80b1e83c13370d02..b12534b3c12020bc96f4175237ee7e9e486828ae 100644 --- a/src/man/sssd.conf.5.xml +++ b/src/man/sssd.conf.5.xml @@ -594,6 +594,44 @@ + + + SUDO configuration options + + These options can be used to configure the sudo service. + + + + + + + sudo_cache_timeout (integer) + + + For any sudo request that comes while SSSD is + online, the SSSD will attempt to update the cached + rules in order to ensure that sudo has the latest + ruleset. + + + The user may, however, run a couple of sudo commands + successively, which would trigger multiple LDAP requests. + In order to speed up this use-case, the sudo service + maintains an in-memory cache that would be used for + performing fast replies. + + + This option controls how long (in seconds) can the sudo + service cache rules for a user. + + + Default: 180 + + + + + + diff --git a/src/responder/sudo/sudosrv.c b/src/responder/sudo/sudosrv.c index 841be43ef757facab894db9ac0b1dc71838bf993..6b7eae07b51a064eeccb2190f45fa9c063a17adf 100644 --- a/src/responder/sudo/sudosrv.c +++ b/src/responder/sudo/sudosrv.c @@ -129,6 +129,27 @@ int sudo_process_init(TALLOC_CTX *mem_ctx, sudo_dp_reconnect_init, iter); } + /* Get responder options */ + + /* Get cache_timeout option */ + ret = confdb_get_int(sudo_ctx->rctx->cdb, sudo_ctx, + CONFDB_SUDO_CONF_ENTRY, CONFDB_SUDO_CACHE_TIMEOUT, + CONFDB_DEFAULT_SUDO_CACHE_TIMEOUT, + &sudo_ctx->cache_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, ("Error reading from confdb (%d) [%s]\n", + ret, strerror(ret))); + return ret; + } + + /* Initialize in-memory cache */ + ret = sudosrv_cache_init(sudo_ctx, 10, &sudo_ctx->cache); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Could not create hash table: [%s]", strerror(ret))); + return ret; + } + DEBUG(SSSDBG_TRACE_FUNC, ("SUDO Initialization complete\n")); return EOK; diff --git a/src/responder/sudo/sudosrv_cache.c b/src/responder/sudo/sudosrv_cache.c new file mode 100644 index 0000000000000000000000000000000000000000..5b91e3b358d9eae97d4fc3151442a17c8d9a54ec --- /dev/null +++ b/src/responder/sudo/sudosrv_cache.c @@ -0,0 +1,284 @@ +/* + Authors: + Pavel Březina + + Copyright (C) 2011 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "util/util.h" +#include "confdb/confdb.h" +#include "db/sysdb.h" +#include "responder/sudo/sudosrv_private.h" + +static void sudosrv_cache_remove(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt); + +struct sudo_cache_entry { + hash_table_t *table; + hash_key_t *key; + size_t num_rules; + struct sysdb_attrs **rules; + + struct sudo_ctx *sudo_ctx; +}; + +errno_t sudosrv_cache_init(TALLOC_CTX *mem_ctx, + unsigned long count, + hash_table_t **table) +{ + return sss_hash_create(mem_ctx, count, table); +} + +static errno_t +sudosrv_cache_reinit(struct sudo_ctx *sudo_ctx) +{ + errno_t ret; + + talloc_free(sudo_ctx->cache); + + ret = sudosrv_cache_init(sudo_ctx, 10, &sudo_ctx->cache); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + ("Could not re-initialize hash table: [%s]", strerror(ret))); + } + return ret; +} + +static hash_key_t *sudosrv_cache_create_key(TALLOC_CTX *mem_ctx, + struct sss_domain_info *domain, + const char *username) +{ + hash_key_t *key = talloc_zero(NULL, hash_key_t); + if (key == NULL) { + return NULL; + } + + key->type = HASH_KEY_STRING; + if (username == NULL) { + key->str = talloc_strdup(key, domain->name); + } else { + key->str = talloc_asprintf(key, "%s:%s", domain->name, username); + } + + if (key->str == NULL) { + talloc_free(key); + return NULL; + } + + return talloc_steal(mem_ctx, key); +} + +errno_t sudosrv_cache_set_entry(struct tevent_context *ev, + struct sudo_ctx *sudo_ctx, + hash_table_t *table, + struct sss_domain_info *domain, + const char *username, + size_t num_rules, + struct sysdb_attrs **rules, + time_t timeout) +{ + struct sudo_cache_entry *cache_entry = NULL; + hash_key_t *key = NULL; + hash_value_t value; + TALLOC_CTX *tmp_ctx = NULL; + struct tevent_timer *timer = NULL; + struct timeval tv; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + goto done; + } + + /* create key */ + key = sudosrv_cache_create_key(tmp_ctx, domain, username); + if (key == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create hash key.\n")); + ret = ENOMEM; + goto done; + } + + /* create value */ + cache_entry = talloc_zero(tmp_ctx, struct sudo_cache_entry); + if (cache_entry == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create hash value.\n")); + ret = ENOMEM; + goto done; + } + cache_entry->table = table; + cache_entry->key = key; + cache_entry->num_rules = num_rules; + cache_entry->rules = rules; + cache_entry->sudo_ctx = sudo_ctx; + + value.type = HASH_VALUE_PTR; + value.ptr = cache_entry; + + /* insert value */ + hret = hash_enter(table, key, &value); + if (hret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, + ("Unable to add [%s] to SUDO cache", key->str)); + DEBUG(SSSDBG_TRACE_LIBS, + ("Hash error [%d][%s]", hret, hash_error_string(hret))); + ret = EIO; + goto done; + } + + /* Create a timer event to remove the entry from the cache */ + tv = tevent_timeval_current_ofs(timeout, 0); + timer = tevent_add_timer(ev, cache_entry, tv, + sudosrv_cache_remove, + cache_entry); + if (timer == NULL) { + ret = ENOMEM; + goto done; + } + + /* everythig is ok, steal the pointers */ + talloc_steal(cache_entry, key); + talloc_steal(cache_entry, rules); + talloc_steal(table, cache_entry); + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +static void sudosrv_cache_remove(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval tv, + void *pvt) +{ + int hret; + hash_key_t *key = NULL; + struct sudo_cache_entry *cache_entry = NULL; + + cache_entry = talloc_get_type(pvt, struct sudo_cache_entry); + key = cache_entry->key; + + hret = hash_delete(cache_entry->table, key); + if (hret != HASH_SUCCESS && hret != HASH_ERROR_KEY_NOT_FOUND + && hret != HASH_ERROR_BAD_KEY_TYPE) { + DEBUG(SSSDBG_MINOR_FAILURE, + ("Could not clear [%s] from SUDO cache.\n", key->str)); + DEBUG(SSSDBG_TRACE_LIBS, + ("Hash error [%d][%s]", hret, hash_error_string(hret))); + + /* corrupted memory, re-initialize table */ + sudosrv_cache_reinit(cache_entry->sudo_ctx); + } else { + DEBUG(SSSDBG_TRACE_INTERNAL, + ("[%s] removed from SUDO cache\n", key->str)); + + talloc_free(cache_entry); + } +} + +static errno_t sudosrv_cache_lookup_internal(hash_table_t *table, + struct sss_domain_info *domain, + const char *username, + size_t *num_rules, + struct sysdb_attrs ***rules) +{ + struct sudo_cache_entry *cache_entry = NULL; + hash_key_t *key = NULL; + hash_value_t value; + TALLOC_CTX *tmp_ctx = NULL; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + goto done; + } + + /* create key */ + key = sudosrv_cache_create_key(tmp_ctx, domain, username); + if (key == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, ("Unable to create hash key.\n")); + ret = ENOMEM; + goto done; + } + + hret = hash_lookup(table, key, &value); + if (hret == HASH_SUCCESS) { + /* cache hit */ + cache_entry = value.ptr; + *num_rules = cache_entry->num_rules; + *rules = cache_entry->rules; + ret = EOK; + } else if (hret == HASH_ERROR_KEY_NOT_FOUND) { + /* cache miss */ + ret = ENOENT; + } else { + /* error */ + ret = EIO; + } + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t sudosrv_cache_lookup(hash_table_t *table, + struct sudo_dom_ctx *dctx, + bool check_next, + const char *username, + size_t *num_rules, + struct sysdb_attrs ***rules) +{ + struct sss_domain_info *domain = dctx->domain; + errno_t ret; + + if (!check_next) { + return sudosrv_cache_lookup_internal(table, dctx->domain, username, + num_rules, rules); + } + + while (domain != NULL) { + if (domain->fqnames) { + domain = domain->next; + continue; + } + + ret = sudosrv_cache_lookup_internal(table, domain, username, + num_rules, rules); + if (ret == EOK) { + /* user is in this domain */ + dctx->domain = domain; + return ret; + } else if (ret != ENOENT) { + /* error */ + return ret; + } + + /* user is not in this domain cache, check next */ + domain = domain->next; + } + + /* user is not in cache */ + return ENOENT; +} diff --git a/src/responder/sudo/sudosrv_cmd.c b/src/responder/sudo/sudosrv_cmd.c index 3550e8bafaa4a7871b8f61c3c54dd08bddd6e870..cef245fec22072ac3ab0e415af2fb5646e29b5e9 100644 --- a/src/responder/sudo/sudosrv_cmd.c +++ b/src/responder/sudo/sudosrv_cmd.c @@ -151,6 +151,15 @@ static int sudosrv_cmd_get_sudorules(struct cli_ctx *cli_ctx) cmd_ctx->cli_ctx = cli_ctx; cmd_ctx->type = SSS_DP_SUDO_USER; + /* get responder ctx */ + cmd_ctx->sudo_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sudo_ctx); + if (!cmd_ctx->sudo_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, ("sudo_ctx not set\n")); + ret = EFAULT; + goto done; + } + + /* create domain ctx */ dctx = talloc_zero(cmd_ctx, struct sudo_dom_ctx); if (!dctx) { ret = ENOMEM; @@ -201,8 +210,18 @@ static int sudosrv_cmd_get_sudorules(struct cli_ctx *cli_ctx) cmd_ctx->check_next = true; } - /* ok, find it ! */ - ret = sudosrv_get_sudorules(dctx); + /* try to find rules in in-memory cache */ + ret = sudosrv_cache_lookup(cmd_ctx->sudo_ctx->cache, dctx, + cmd_ctx->check_next, cmd_ctx->username, + &dctx->res_count, &dctx->res); + if (ret == EOK) { + /* cache hit */ + DEBUG(SSSDBG_FUNC_DATA, ("Returning rules for [%s@%s] " + "from in-memory cache\n", cmd_ctx->username, dctx->domain->name)); + } else if (ret == ENOENT) { + /* cache expired or missed */ + ret = sudosrv_get_sudorules(dctx); + } /* else error */ done: return sudosrv_cmd_done(dctx, ret); @@ -224,6 +243,15 @@ static int sudosrv_cmd_get_defaults(struct cli_ctx *cli_ctx) cmd_ctx->username = NULL; cmd_ctx->check_next = false; + /* get responder ctx */ + cmd_ctx->sudo_ctx = talloc_get_type(cli_ctx->rctx->pvt_ctx, struct sudo_ctx); + if (!cmd_ctx->sudo_ctx) { + DEBUG(SSSDBG_FATAL_FAILURE, ("sudo_ctx not set\n")); + ret = EFAULT; + goto done; + } + + /* create domain ctx */ dctx = talloc_zero(cmd_ctx, struct sudo_dom_ctx); if (!dctx) { ret = ENOMEM; @@ -246,8 +274,18 @@ static int sudosrv_cmd_get_defaults(struct cli_ctx *cli_ctx) goto done; } - /* ok, find it ! */ - ret = sudosrv_get_rules(dctx); + /* try to find rules in in-memory cache */ + ret = sudosrv_cache_lookup(cmd_ctx->sudo_ctx->cache, dctx, + cmd_ctx->check_next, cmd_ctx->username, + &dctx->res_count, &dctx->res); + if (ret == EOK) { + /* cache hit */ + DEBUG(SSSDBG_FUNC_DATA, ("Returning defaults settings for [%s] " + "from in-memory cache\n", dctx->domain->name)); + } else if (ret == ENOENT) { + /* cache expired or missed */ + ret = sudosrv_get_rules(dctx); + } /* else error */ done: return sudosrv_cmd_done(dctx, ret); diff --git a/src/responder/sudo/sudosrv_get_sudorules.c b/src/responder/sudo/sudosrv_get_sudorules.c index 8cb10e9aa336ce0c29aa3bef9efd90f695209bb9..c53638a359b22d2479bcf06fbe1cef86a6ae5bc2 100644 --- a/src/responder/sudo/sudosrv_get_sudorules.c +++ b/src/responder/sudo/sudosrv_get_sudorules.c @@ -210,7 +210,6 @@ static void sudosrv_check_user_dp_callback(uint16_t err_maj, uint32_t err_min, ("Data Provider returned, check the cache again\n")); dctx->check_provider = false; ret = sudosrv_get_user(dctx); - /* FIXME - set entry into cache so that we don't perform initgroups too often */ if (ret == EAGAIN) { goto done; } else if (ret != EOK) { @@ -250,8 +249,9 @@ errno_t sudosrv_get_rules(struct sudo_dom_ctx *dctx) struct sudo_cmd_ctx *cmd_ctx = dctx->cmd_ctx; struct dp_callback_ctx *cb_ctx = NULL; - /* FIXME - cache logic will be here. For now, just refresh - * the cache unconditionally */ + DEBUG(SSSDBG_TRACE_FUNC, ("getting rules for %s\n", + cmd_ctx->username ? cmd_ctx->username : "default options")); + dpreq = sss_dp_get_sudoers_send(cmd_ctx->cli_ctx, cmd_ctx->cli_ctx->rctx, dctx->domain, false, @@ -319,7 +319,6 @@ sudosrv_get_sudorules_dp_callback(uint16_t err_maj, uint32_t err_min, "Will try to return what we have in cache\n", (unsigned int)err_maj, (unsigned int)err_min, err_msg)); - /* FIXME - cache or next domain? */ /* Loop to the next domain if possible */ if (dctx->domain->next && dctx->cmd_ctx->check_next) { dctx->domain = dctx->domain->next; @@ -355,8 +354,11 @@ static errno_t sudosrv_get_sudorules_from_cache(struct sudo_dom_ctx *dctx) errno_t ret; struct sysdb_ctx *sysdb; struct cli_ctx *cli_ctx = dctx->cmd_ctx->cli_ctx; + struct sudo_ctx *sudo_ctx = dctx->cmd_ctx->sudo_ctx; uid_t uid; char **groupnames; + const char *safe_name = dctx->cmd_ctx->username ? + dctx->cmd_ctx->username : "default rules"; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) return ENOMEM; @@ -393,8 +395,21 @@ static errno_t sudosrv_get_sudorules_from_cache(struct sudo_dom_ctx *dctx) goto done; } + /* Store result in in-memory cache */ + ret = sudosrv_cache_set_entry(sudo_ctx->rctx->ev, sudo_ctx, + sudo_ctx->cache, dctx->domain, + dctx->cmd_ctx->username, dctx->res_count, + dctx->res, sudo_ctx->cache_timeout); + if (ret != EOK) { + DEBUG(SSSDBG_MINOR_FAILURE, ("Unable to store rules in cache for " + "[%s@%s]\n", safe_name, dctx->domain->name)); + } else { + DEBUG(SSSDBG_FUNC_DATA, ("Rules for [%s@%s] stored in in-memory cache\n", + safe_name, dctx->domain->name)); + } + DEBUG(SSSDBG_TRACE_FUNC, ("Returning rules for [%s@%s]\n", - dctx->cmd_ctx->username, dctx->domain->name)); + safe_name, dctx->domain->name)); ret = EOK; done: diff --git a/src/responder/sudo/sudosrv_private.h b/src/responder/sudo/sudosrv_private.h index b59aca4a3b152211724cb405b892ae098a3ba139..c3feb19bf797a706176f4b41b51c2edcade1cfed 100644 --- a/src/responder/sudo/sudosrv_private.h +++ b/src/responder/sudo/sudosrv_private.h @@ -38,10 +38,23 @@ enum sss_dp_sudo_type { struct sudo_ctx { struct resp_ctx *rctx; + + /* + * options + */ + int cache_timeout; + + /* + * Key: domain for SSS_DP_SUDO_DEFAULTS + * domain:username for SSS_DP_SUDO_USER + * Val: struct sudo_cache_entry * + */ + hash_table_t *cache; }; struct sudo_cmd_ctx { struct cli_ctx *cli_ctx; + struct sudo_ctx *sudo_ctx; enum sss_dp_sudo_type type; char *username; bool check_next; @@ -121,4 +134,24 @@ sss_dp_get_sudoers_recv(TALLOC_CTX *mem_ctx, dbus_uint32_t *err_min, char **err_msg); +errno_t sudosrv_cache_init(TALLOC_CTX *mem_ctx, + unsigned long count, + hash_table_t **table); + +errno_t sudosrv_cache_lookup(hash_table_t *table, + struct sudo_dom_ctx *dctx, + bool check_next, + const char *username, + size_t *res_count, + struct sysdb_attrs ***res); + +errno_t sudosrv_cache_set_entry(struct tevent_context *ev, + struct sudo_ctx *sudo_ctx, + hash_table_t *table, + struct sss_domain_info *domain, + const char *username, + size_t res_count, + struct sysdb_attrs **res, + time_t timeout); + #endif /* _SUDOSRV_PRIVATE_H_ */ -- 1.7.7.6