Skip to content

Commit

Permalink
KCM: Remove the oldest expired credential if no more space.
Browse files Browse the repository at this point in the history
:feature: When adding a new credential to KCM and the user has
          already reached their limit, the oldest expired credential
          will be removed to free some space.
          If no expired credential is found to be removed, the operation
          will fail as it happened in the previous versions.

Resolves: #6667
  • Loading branch information
aplopez committed Oct 3, 2023
1 parent 66c0a2d commit 2af0775
Showing 1 changed file with 189 additions and 17 deletions.
206 changes: 189 additions & 17 deletions src/responder/kcm/secrets/secrets.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <uuid/uuid.h>

#include "config.h"

#include "responder/kcm/kcmsrv_ccache.h"
#include "util/util.h"
#include "util/util_creds.h"
#include "util/sss_iobuf.h"
#include "util/strtonum.h"
#include "util/crypto/sss_crypto.h"
#include "sec_pvt.h"
Expand All @@ -50,6 +53,10 @@ static struct sss_sec_quota default_kcm_quota = {
.containers_nest_level = DEFAULT_SEC_CONTAINERS_NEST_LEVEL,
};

static char *local_dn_to_path(TALLOC_CTX *mem_ctx,
struct ldb_dn *basedn,
struct ldb_dn *dn);

static int local_db_check_containers(TALLOC_CTX *mem_ctx,
struct sss_sec_ctx *sec_ctx,
struct ldb_dn *leaf_dn)
Expand Down Expand Up @@ -181,11 +188,169 @@ static struct ldb_dn *per_uid_container(TALLOC_CTX *mem_ctx,
return uid_base_dn;
}

static errno_t get_secret_expiration_time(uint8_t *key, size_t key_length,
uint8_t *sec, size_t sec_length,
time_t *_expiration)
{
errno_t ret;
TALLOC_CTX *tmp_ctx;
time_t expiration = 0;
struct cli_creds client = {};
struct kcm_ccache *cc;
struct sss_iobuf *iobuf;
krb5_creds **cred_list, **cred;
const char *key_str;

if (_expiration == NULL) {
return EINVAL;
}

tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
return ENOMEM;
}

key_str = talloc_strndup(tmp_ctx, (const char *) key, key_length);
if (key_str == NULL) {
ret = ENOMEM;
goto done;
}

iobuf = sss_iobuf_init_readonly(tmp_ctx, sec, sec_length);
if (iobuf == NULL) {
ret = ENOMEM;
goto done;
}

ret = sec_kv_to_ccache_binary(tmp_ctx, key_str, iobuf, &client, &cc);
if (ret != EOK) {
goto done;
}

cred_list = kcm_cc_unmarshal(tmp_ctx, NULL, cc);
if (cred_list == NULL) {
ret = ENOMEM;
goto done;
}

for (cred = cred_list; *cred != NULL; cred++) {
if ((*cred)->times.endtime != 0) {
expiration = (time_t) (*cred)->times.endtime;
break;
}
}

*_expiration = expiration;
ret = EOK;

done:
talloc_free(tmp_ctx);
return ret;
}

static errno_t local_db_remove_oldest_expired_secret(struct ldb_result *res,
struct sss_sec_req *req)
{
TALLOC_CTX *tmp_ctx;
struct sss_sec_req *new_req;
const struct ldb_val *val;
const struct ldb_val *rdn;
struct ldb_message *msg;
struct ldb_message_element *elem;
struct ldb_dn *basedn;
struct ldb_dn *oldest_dn = NULL;
time_t oldest_time = time(NULL);
time_t expiration;
unsigned int i;
int ret;

DEBUG(SSSDBG_TRACE_INTERNAL, "Removing the oldest expired credential\n");
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate memory\n");
return ENOMEM;
}

// Between all the messages in result, there is also the key we are
// currently treating, but because yet it doesn't have an expiration time,
// it will be skipped.
for (i = 0; i < res->count; i++) {
msg = res->msgs[i];

/* Skip cn=default,... or any non cn=... */
rdn = ldb_dn_get_rdn_val(msg->dn);
if (strcmp(ldb_dn_get_rdn_name(msg->dn), "cn") != 0
|| strncmp("default", (char *) rdn->data, rdn->length) == 0) {
continue;
}

elem = ldb_msg_find_element(msg, SEC_ATTR_SECRET);
if (elem != NULL) {
if (elem->num_values != 1) {
DEBUG(SSSDBG_MINOR_FAILURE,
"Element %s has %u values. Ignoring it.\n",
SEC_ATTR_SECRET, elem->num_values);
ret = ERR_MALFORMED_ENTRY;
goto done;
}

val = &elem->values[0];
ret = get_secret_expiration_time(rdn->data, rdn->length,
val->data, val->length,
&expiration);
if (ret != EOK) {
goto done;
}
if (expiration > 0 && expiration < oldest_time) {
oldest_dn = msg->dn;
oldest_time = expiration;
}
}
}

if (oldest_dn == NULL) {
DEBUG(SSSDBG_TRACE_INTERNAL, "Found no expired credential to remove\n");
ret = ERR_NO_MATCHING_CREDS;
goto done;
}

basedn = ldb_dn_new(tmp_ctx, req->sctx->ldb, req->basedn);
if (basedn == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to create a dn: %s\n", req->basedn);
ret = EINVAL;
goto done;
}

new_req = talloc_zero(tmp_ctx, struct sss_sec_req);
if (new_req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to allocate the new request\n");
return ENOMEM;
}

new_req->basedn = req->basedn;
new_req->quota = req->quota;
new_req->req_dn = oldest_dn;
new_req->sctx = req->sctx;
new_req->path = local_dn_to_path(new_req, basedn, oldest_dn);
if (new_req->path == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to create the path\n");
ret = EINVAL;
goto done;
}

ret = sss_sec_delete(new_req);

done:
talloc_free(tmp_ctx);
return ret;
}


static int local_db_check_peruid_number_of_secrets(TALLOC_CTX *mem_ctx,
struct sss_sec_req *req)
{
TALLOC_CTX *tmp_ctx;
static const char *attrs[] = { NULL };
static const char *attrs[] = { SEC_ATTR_SECRET, NULL };
struct ldb_result *res = NULL;
struct ldb_dn *cli_basedn = NULL;
int ret;
Expand Down Expand Up @@ -214,13 +379,20 @@ static int local_db_check_peruid_number_of_secrets(TALLOC_CTX *mem_ctx,
}

if (res->count >= req->quota->max_uid_secrets) {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot store any more secrets for this client (basedn %s) "
"as the maximum allowed limit (%d) has been reached\n",
ldb_dn_get_linearized(cli_basedn),
req->quota->max_uid_secrets);
ret = ERR_SEC_INVALID_TOO_MANY_SECRETS;
goto done;
/* We reached the limit. Let's try to removed the
* oldest expired credential to free some space. */
ret = local_db_remove_oldest_expired_secret(res, req);
if (ret != EOK) {
if (ret == ERR_NO_MATCHING_CREDS) {
DEBUG(SSSDBG_OP_FAILURE,
"Cannot store any more secrets for this client (basedn %s) "
"as the maximum allowed limit (%d) has been reached\n",
ldb_dn_get_linearized(cli_basedn),
req->quota->max_uid_secrets);
ret = ERR_SEC_INVALID_TOO_MANY_SECRETS;
}
goto done;
}
}

ret = EOK;
Expand Down Expand Up @@ -808,15 +980,15 @@ errno_t sss_sec_put(struct sss_sec_req *req,
goto done;
}

ret = local_db_check_number_of_secrets(msg, req);
ret = local_db_check_peruid_number_of_secrets(msg, req);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"local_db_check_number_of_secrets failed [%d]: %s\n",
"local_db_check_peruid_number_of_secrets failed [%d]: %s\n",
ret, sss_strerror(ret));
goto done;
}

ret = local_db_check_peruid_number_of_secrets(msg, req);
ret = local_db_check_number_of_secrets(msg, req);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"local_db_check_number_of_secrets failed [%d]: %s\n",
Expand Down Expand Up @@ -905,15 +1077,15 @@ errno_t sss_sec_update(struct sss_sec_req *req,
goto done;
}

ret = local_db_check_number_of_secrets(msg, req);
ret = local_db_check_peruid_number_of_secrets(msg, req);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"local_db_check_number_of_secrets failed [%d]: %s\n",
"local_db_check_peruid_number_of_secrets failed [%d]: %s\n",
ret, sss_strerror(ret));
goto done;
}

ret = local_db_check_peruid_number_of_secrets(msg, req);
ret = local_db_check_number_of_secrets(msg, req);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"local_db_check_number_of_secrets failed [%d]: %s\n",
Expand Down

0 comments on commit 2af0775

Please sign in to comment.