Blob Blame History Raw
From 73a04a5c53c0e7701aa7753fd459ffbea52e28b8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
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 <pbrezina@redhat.com>
Reviewed-by: Fabiano Fidêncio <fidencio@redhat.com>
(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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <security/pam_appl.h>
+
+#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 <security/pam_misc.h>
+#elif defined(HAVE_SECURITY_OPENPAM_H)
+# include <security/openpam.h>
+#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