From 73a04a5c53c0e7701aa7753fd459ffbea52e28b8 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 23 Oct 2017 18:08:12 +0200 Subject: [PATCH 48/57] TOOLS: Add a new sssctl command access-report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves: https://pagure.io/SSSD/sssd/issue/2840 Reviewed-by: Pavel Březina Reviewed-by: Fabiano Fidêncio (cherry picked from commit 3ee8659bc6a77a78bc6c61b9650a36bd18ea95c8) --- Makefile.am | 1 + src/tools/sssctl/sssctl.c | 1 + src/tools/sssctl/sssctl.h | 5 + src/tools/sssctl/sssctl_access_report.c | 435 ++++++++++++++++++++++++++++++++ 4 files changed, 442 insertions(+) create mode 100644 src/tools/sssctl/sssctl_access_report.c diff --git a/Makefile.am b/Makefile.am index 16bcb4efc028b05c1196249245f4f3091b9366af..5917bd904054055a259eb69217282e4fb914c700 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1754,6 +1754,7 @@ sssctl_SOURCES = \ src/tools/sssctl/sssctl_sifp.c \ src/tools/sssctl/sssctl_config.c \ src/tools/sssctl/sssctl_user_checks.c \ + src/tools/sssctl/sssctl_access_report.c \ $(SSSD_TOOLS_OBJ) \ $(NULL) sssctl_LDADD = \ diff --git a/src/tools/sssctl/sssctl.c b/src/tools/sssctl/sssctl.c index 1e061c00d2238bf34adff4183e560dc127dd62c7..eee2d613966a5dda81627d2e225bfdc9bade4041 100644 --- a/src/tools/sssctl/sssctl.c +++ b/src/tools/sssctl/sssctl.c @@ -264,6 +264,7 @@ int main(int argc, const char **argv) SSS_TOOL_COMMAND("domain-list", "List available domains", 0, sssctl_domain_list), SSS_TOOL_COMMAND("domain-status", "Print information about domain", 0, sssctl_domain_status), SSS_TOOL_COMMAND("user-checks", "Print information about a user and check authentication", 0, sssctl_user_checks), + SSS_TOOL_COMMAND("access-report", "Generate access report for a domain", 0, sssctl_access_report), SSS_TOOL_DELIMITER("Information about cached content:"), SSS_TOOL_COMMAND("user-show", "Information about cached user", 0, sssctl_user_show), SSS_TOOL_COMMAND("group-show", "Information about cached group", 0, sssctl_group_show), diff --git a/src/tools/sssctl/sssctl.h b/src/tools/sssctl/sssctl.h index 22ca5d41e2c084e64b58bc5aa066414b002e7e8b..70fc19eff07317c264978a1ecb9159ae3acdfced 100644 --- a/src/tools/sssctl/sssctl.h +++ b/src/tools/sssctl/sssctl.h @@ -133,4 +133,9 @@ errno_t sssctl_config_check(struct sss_cmdline *cmdline, errno_t sssctl_user_checks(struct sss_cmdline *cmdline, struct sss_tool_ctx *tool_ctx, void *pvt); + +errno_t sssctl_access_report(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + #endif /* _SSSCTL_H_ */ diff --git a/src/tools/sssctl/sssctl_access_report.c b/src/tools/sssctl/sssctl_access_report.c new file mode 100644 index 0000000000000000000000000000000000000000..11172329817b4dedaca480ab8a4537149853c330 --- /dev/null +++ b/src/tools/sssctl/sssctl_access_report.c @@ -0,0 +1,435 @@ +/* + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#include + +#include "util/util.h" +#include "tools/common/sss_tools.h" +#include "tools/sssctl/sssctl.h" + +/* + * We're searching the cache directly.. + */ +#include "providers/ipa/ipa_hbac_private.h" +#include "providers/ipa/ipa_rules_common.h" + +#ifdef HAVE_SECURITY_PAM_MISC_H +# include +#elif defined(HAVE_SECURITY_OPENPAM_H) +# include +#endif + +#ifdef HAVE_SECURITY_PAM_MISC_H +static struct pam_conv conv = { + misc_conv, + NULL +}; +#elif defined(HAVE_SECURITY_OPENPAM_H) +static struct pam_conv conv = { + openpam_ttyconv, + NULL +}; +#else +# error "Missing text based pam conversation function" +#endif + +#ifndef DEFAULT_SERVICE +#define DEFAULT_SERVICE "system-auth" +#endif /* DEFAULT_SERVICE */ + +#ifndef DEFAULT_USER +#define DEFAULT_USER "admin" +#endif /* DEFAULT_USER */ + +typedef errno_t (*sssctl_dom_access_reporter_fn)(struct sss_tool_ctx *tool_ctx, + const char *user, + const char *service, + struct sss_domain_info *domain); + +static errno_t run_pam_acct(struct sss_tool_ctx *tool_ctx, + const char *user, + const char *service, + struct sss_domain_info *domain) +{ + errno_t ret; + pam_handle_t *pamh; + + ret = pam_start(service, user, &conv, &pamh); + if (ret != PAM_SUCCESS) { + ERROR("pam_start failed: %s\n", pam_strerror(pamh, ret)); + return EIO; + } + + ret = pam_acct_mgmt(pamh, 0); + pam_end(pamh, ret); + return ret; +} + +static errno_t get_rdn_value(TALLOC_CTX *mem_ctx, + struct sss_domain_info *dom, + const char *dn_attr, + const char **_rdn_value) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx; + struct ldb_dn *dn = NULL; + const struct ldb_val *rdn_val; + const char *rdn_str; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(dom->sysdb), dn_attr); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + rdn_val = ldb_dn_get_rdn_val(dn); + if (rdn_val == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "No RDN value?\n"); + ret = ENOMEM; + goto done; + } + + rdn_str = talloc_strndup(tmp_ctx, + (const char *)rdn_val->data, + rdn_val->length); + if (rdn_str == NULL) { + ret = ENOMEM; + goto done; + } + + ret = EOK; + *_rdn_value = talloc_steal(mem_ctx, rdn_str); +done: + talloc_zfree(tmp_ctx); + return ret; +} + +static errno_t is_member_group(struct sss_domain_info *dom, + const char *dn_attr, + const char *group_rdn, + bool *_is_group) +{ + const char *comp_name; + const struct ldb_val *comp_val; + TALLOC_CTX *tmp_ctx; + bool is_group = false; + errno_t ret; + struct ldb_dn *dn = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + dn = ldb_dn_new(tmp_ctx, sysdb_ctx_get_ldb(dom->sysdb), dn_attr); + if (dn == NULL) { + ret = ENOMEM; + goto done; + } + + comp_name = ldb_dn_get_component_name(dn, 1); + comp_val = ldb_dn_get_component_val(dn, 1); + if (strcasecmp("cn", comp_name) == 0 + && strncasecmp(group_rdn, + (const char *) comp_val->data, + comp_val->length) == 0) { + is_group = true; + } + + ret = EOK; +done: + *_is_group = is_group; + talloc_zfree(tmp_ctx); + return ret; +} + +static void print_category(struct sss_domain_info *domain, + struct ldb_message *rule_msg, + const char *category_attr_name, + const char *category_label) +{ + struct ldb_message_element *category_attr; + + category_attr = ldb_msg_find_element(rule_msg, category_attr_name); + if (category_attr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find %s\n", category_attr_name); + return; + } + + if (category_attr->num_values > 0) { + PRINT("\t%s: ", category_label); + for (unsigned i = 0; i < category_attr->num_values; i++) { + PRINT("%s%s", + i > 0 ? ", " : "", + (const char *) category_attr->values[i].data); + } + PRINT("\n"); + } +} + +static void print_member_attr(struct sss_domain_info *domain, + struct ldb_message *rule_msg, + const char *member_attr_name, + const char *group_rdn, + const char *object_label, + const char *group_label) +{ + errno_t ret; + TALLOC_CTX *tmp_ctx = NULL; + const char **member_names = NULL; + size_t name_count = 0; + const char **member_group_names = NULL; + size_t group_count = 0; + struct ldb_message_element *member_attr = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return; + } + + member_attr = ldb_msg_find_element(rule_msg, member_attr_name); + if (member_attr == NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Cannot find %s\n", member_attr_name); + goto done; + } + + member_names = talloc_zero_array(tmp_ctx, + const char *, + member_attr->num_values + 1); + member_group_names = talloc_zero_array(tmp_ctx, + const char *, + member_attr->num_values + 1); + if (member_names == NULL || member_group_names == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "OOM?\n"); + goto done; + } + + for (size_t i = 0; i < member_attr->num_values; i++) { + bool is_group; + const char *rdn_string; + const char *dn_attr; + + dn_attr = (const char *) member_attr->values[i].data; + + ret = is_member_group(domain, dn_attr, group_rdn, &is_group); + if (ret != EOK) { + continue; + } + + ret = get_rdn_value(tmp_ctx, domain, dn_attr, &rdn_string); + if (ret != EOK) { + continue; + } + + if (is_group == false) { + member_names[name_count] = talloc_steal(member_names, + rdn_string); + if (member_names[name_count] == NULL) { + goto done; + } + name_count++; + } else { + member_group_names[group_count] = talloc_strdup(member_group_names, + rdn_string); + if (member_group_names[group_count] == NULL) { + goto done; + } + group_count++; + } + } + + if (member_names[0] != NULL) { + PRINT("\t%s: ", object_label); + for (int i = 0; member_names[i]; i++) { + PRINT("%s%s", i > 0 ? ", " : "", member_names[i]); + } + PRINT("\n"); + } + + if (member_group_names[0] != NULL) { + PRINT("\t%s: ", group_label); + for (int i = 0; member_group_names[i]; i++) { + PRINT("%s%s", i > 0 ? ", " : "", member_group_names[i]); + } + PRINT("\n"); + } + +done: + talloc_free(tmp_ctx); +} + +static void print_ipa_hbac_rule(struct sss_domain_info *domain, + struct ldb_message *rule_msg) +{ + struct ldb_message_element *el; + + el = ldb_msg_find_element(rule_msg, IPA_CN); + if (el == NULL || el->num_values < 1) { + DEBUG(SSSDBG_MINOR_FAILURE, "A rule with no name\n"); + return; + } + + PRINT("Rule name: %1$s\n", el->values[0].data); + + print_member_attr(domain, + rule_msg, + IPA_MEMBER_USER, + "groups", + _("Member users"), + _("Member groups")); + print_category(domain, + rule_msg, + IPA_USER_CATEGORY, + _("User category")); + + print_member_attr(domain, + rule_msg, + IPA_MEMBER_SERVICE, + "hbacservicegroups", + _("Member services"), + _("Member service groups")); + print_category(domain, + rule_msg, + IPA_SERVICE_CATEGORY, + _("Service category")); + + PRINT("\n"); +} + +static errno_t sssctl_ipa_access_report(struct sss_tool_ctx *tool_ctx, + const char *user, + const char *service, + struct sss_domain_info *domain) +{ + TALLOC_CTX *tmp_ctx = NULL; + const char *filter = NULL; + errno_t ret; + const char *attrs[] = { + OBJECTCLASS, + IPA_CN, + IPA_MEMBER_USER, + IPA_USER_CATEGORY, + IPA_MEMBER_SERVICE, + IPA_SERVICE_CATEGORY, + IPA_MEMBER_HOST, + IPA_HOST_CATEGORY, + NULL, + }; + size_t rule_count; + struct ldb_message **msgs = NULL; + + /* Run the pam account phase to make sure the rules are fetched by SSSD */ + ret = run_pam_acct(tool_ctx, user, service, domain); + if (ret != PAM_SUCCESS && ret != PAM_PERM_DENIED) { + ERROR("Cannot run the PAM account phase, reporting stale rules\n"); + /* Non-fatal */ + } + + tmp_ctx = talloc_new(tool_ctx); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + filter = talloc_asprintf(tmp_ctx, "(objectClass=%s)", IPA_HBAC_RULE); + if (filter == NULL) { + ret = ENOMEM; + goto done; + } + + ret = sysdb_search_custom(tmp_ctx, domain, filter, + HBAC_RULES_SUBDIR, attrs, + &rule_count, &msgs); + if (ret != EOK && ret != ENOENT) { + DEBUG(SSSDBG_CRIT_FAILURE, "Error looking up HBAC rules\n"); + goto done; + } + + if (ret == ENOENT) { + PRINT("No cached rules. All users will be denied access\n"); + ret = EOK; + goto done; + } + + PRINT("%1$zu rules cached\n\n", rule_count); + + for (size_t i = 0; i < rule_count; i++) { + print_ipa_hbac_rule(domain, msgs[i]); + } + + ret = EOK; +done: + talloc_zfree(tmp_ctx); + return ret; +} + +sssctl_dom_access_reporter_fn get_report_fn(const char *provider) +{ + if (strcmp(provider, "ipa") == 0) { + return sssctl_ipa_access_report; + } + + return NULL; +} + +errno_t sssctl_access_report(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + const char *domname = NULL; + sssctl_dom_access_reporter_fn reporter; + struct sss_domain_info *dom; + const char *user = DEFAULT_USER; + const char *service = DEFAULT_SERVICE; + + /* Parse command line. */ + struct poptOption options[] = { + { "user", 'u', POPT_ARG_STRING, &user, 0, + _("PAM user, default: " DEFAULT_USER), NULL }, + { "service", 's', POPT_ARG_STRING, &service, 0, + _("PAM service, default: " DEFAULT_SERVICE), NULL }, + POPT_TABLEEND + }; + + ret = sss_tool_popt_ex(cmdline, options, SSS_TOOL_OPT_OPTIONAL, + NULL, NULL, "DOMAIN", _("Specify domain name."), + &domname, NULL); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to parse command arguments\n"); + return ret; + } + + dom = find_domain_by_name(tool_ctx->domains, domname, true); + if (dom == NULL) { + ERROR("Cannot find domain %1$s\n", domname); + return ERR_DOMAIN_NOT_FOUND; + } + + reporter = get_report_fn(dom->provider); + if (reporter == NULL) { + ERROR("Access report not implemented for domains of type %1$s\n", + dom->provider); + return ret; + } + + return reporter(tool_ctx, user, service, dom); +} -- 2.14.3