LCOV - code coverage report
Current view: top level - common - acl.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 19 344 5.5 %
Date: 2024-05-07 11:09:47 Functions: 3 20 15.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2004-2024 the Pacemaker project contributors
       3             :  *
       4             :  * The version control history for this file may have further details.
       5             :  *
       6             :  * This source code is licensed under the GNU Lesser General Public License
       7             :  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
       8             :  */
       9             : 
      10             : #include <crm_internal.h>
      11             : 
      12             : #include <stdio.h>
      13             : #include <sys/types.h>
      14             : #include <pwd.h>
      15             : #include <string.h>
      16             : #include <stdlib.h>
      17             : #include <stdarg.h>
      18             : 
      19             : #include <libxml/tree.h>
      20             : 
      21             : #include <crm/crm.h>
      22             : #include <crm/common/xml.h>
      23             : #include <crm/common/xml_internal.h>
      24             : #include "crmcommon_private.h"
      25             : 
      26             : typedef struct xml_acl_s {
      27             :         enum xml_private_flags mode;
      28             :         gchar *xpath;
      29             : } xml_acl_t;
      30             : 
      31             : static void
      32           0 : free_acl(void *data)
      33             : {
      34           0 :     if (data) {
      35           0 :         xml_acl_t *acl = data;
      36             : 
      37           0 :         g_free(acl->xpath);
      38           0 :         free(acl);
      39             :     }
      40           0 : }
      41             : 
      42             : void
      43           0 : pcmk__free_acls(GList *acls)
      44             : {
      45           0 :     g_list_free_full(acls, free_acl);
      46           0 : }
      47             : 
      48             : static GList *
      49           0 : create_acl(const xmlNode *xml, GList *acls, enum xml_private_flags mode)
      50             : {
      51           0 :     xml_acl_t *acl = NULL;
      52             : 
      53           0 :     const char *tag = crm_element_value(xml, PCMK_XA_OBJECT_TYPE);
      54           0 :     const char *ref = crm_element_value(xml, PCMK_XA_REFERENCE);
      55           0 :     const char *xpath = crm_element_value(xml, PCMK_XA_XPATH);
      56           0 :     const char *attr = crm_element_value(xml, PCMK_XA_ATTRIBUTE);
      57             : 
      58           0 :     if (tag == NULL) {
      59             :         // @COMPAT Deprecated since 1.1.12 (needed for rolling upgrades)
      60           0 :         tag = crm_element_value(xml, PCMK_XA_TAG);
      61             :     }
      62           0 :     if (ref == NULL) {
      63             :         // @COMPAT Deprecated since 1.1.12 (needed for rolling upgrades)
      64           0 :         ref = crm_element_value(xml, PCMK__XA_REF);
      65             :     }
      66             : 
      67           0 :     if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) {
      68             :         // Schema should prevent this, but to be safe ...
      69           0 :         crm_trace("Ignoring ACL <%s> element without selection criteria",
      70             :                   xml->name);
      71           0 :         return NULL;
      72             :     }
      73             : 
      74           0 :     acl = pcmk__assert_alloc(1, sizeof (xml_acl_t));
      75             : 
      76           0 :     acl->mode = mode;
      77           0 :     if (xpath) {
      78           0 :         acl->xpath = g_strdup(xpath);
      79           0 :         crm_trace("Unpacked ACL <%s> element using xpath: %s",
      80             :                   xml->name, acl->xpath);
      81             : 
      82             :     } else {
      83           0 :         GString *buf = g_string_sized_new(128);
      84             : 
      85           0 :         if ((ref != NULL) && (attr != NULL)) {
      86             :             // NOTE: schema currently does not allow this
      87           0 :             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@" PCMK_XA_ID "='",
      88             :                            ref, "' and @", attr, "]", NULL);
      89             : 
      90           0 :         } else if (ref != NULL) {
      91           0 :             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@" PCMK_XA_ID "='",
      92             :                            ref, "']", NULL);
      93             : 
      94           0 :         } else if (attr != NULL) {
      95           0 :             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@", attr, "]", NULL);
      96             : 
      97             :         } else {
      98           0 :             pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), NULL);
      99             :         }
     100             : 
     101           0 :         acl->xpath = buf->str;
     102             : 
     103           0 :         g_string_free(buf, FALSE);
     104           0 :         crm_trace("Unpacked ACL <%s> element as xpath: %s",
     105             :                   xml->name, acl->xpath);
     106             :     }
     107             : 
     108           0 :     return g_list_append(acls, acl);
     109             : }
     110             : 
     111             : /*!
     112             :  * \internal
     113             :  * \brief Unpack a user, group, or role subtree of the ACLs section
     114             :  *
     115             :  * \param[in]     acl_top    XML of entire ACLs section
     116             :  * \param[in]     acl_entry  XML of ACL element being unpacked
     117             :  * \param[in,out] acls       List of ACLs unpacked so far
     118             :  *
     119             :  * \return New head of (possibly modified) acls
     120             :  *
     121             :  * \note This function is recursive
     122             :  */
     123             : static GList *
     124           0 : parse_acl_entry(const xmlNode *acl_top, const xmlNode *acl_entry, GList *acls)
     125             : {
     126           0 :     xmlNode *child = NULL;
     127             : 
     128           0 :     for (child = pcmk__xe_first_child(acl_entry, NULL, NULL, NULL);
     129           0 :          child != NULL; child = pcmk__xe_next(child)) {
     130             : 
     131           0 :         const char *tag = (const char *) child->name;
     132           0 :         const char *kind = crm_element_value(child, PCMK_XA_KIND);
     133             : 
     134           0 :         if (pcmk__xe_is(child, PCMK_XE_ACL_PERMISSION)) {
     135           0 :             CRM_ASSERT(kind != NULL);
     136           0 :             crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind);
     137           0 :             tag = kind;
     138             :         } else {
     139           0 :             crm_trace("Unpacking ACL <%s> element", tag);
     140             :         }
     141             : 
     142             :         /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed
     143             :          * for rolling upgrades)
     144             :          */
     145           0 :         if (pcmk__str_any_of(tag, PCMK_XE_ROLE, PCMK__XE_ROLE_REF, NULL)) {
     146           0 :             const char *ref_role = crm_element_value(child, PCMK_XA_ID);
     147             : 
     148           0 :             if (ref_role) {
     149           0 :                 xmlNode *role = NULL;
     150             : 
     151           0 :                 for (role = pcmk__xe_first_child(acl_top, NULL, NULL, NULL);
     152           0 :                      role != NULL; role = pcmk__xe_next(role)) {
     153             : 
     154           0 :                     if (!strcmp(PCMK_XE_ACL_ROLE, (const char *) role->name)) {
     155           0 :                         const char *role_id = crm_element_value(role,
     156             :                                                                 PCMK_XA_ID);
     157             : 
     158           0 :                         if (role_id && strcmp(ref_role, role_id) == 0) {
     159           0 :                             crm_trace("Unpacking referenced role '%s' in ACL <%s> element",
     160             :                                       role_id, acl_entry->name);
     161           0 :                             acls = parse_acl_entry(acl_top, role, acls);
     162           0 :                             break;
     163             :                         }
     164             :                     }
     165             :                 }
     166             :             }
     167             : 
     168             :         /* @COMPAT Use of a tag instead of a PCMK_XA_KIND attribute was
     169             :          * deprecated in 1.1.12. We still need to look for tags named
     170             :          * PCMK_VALUE_READ, etc., to support rolling upgrades. However,
     171             :          * eventually we can clean this up and make the variables more intuitive
     172             :          * (for example, don't assign a PCMK_XA_KIND value to the tag variable).
     173             :          */
     174           0 :         } else if (strcmp(tag, PCMK_VALUE_READ) == 0) {
     175           0 :             acls = create_acl(child, acls, pcmk__xf_acl_read);
     176             : 
     177           0 :         } else if (strcmp(tag, PCMK_VALUE_WRITE) == 0) {
     178           0 :             acls = create_acl(child, acls, pcmk__xf_acl_write);
     179             : 
     180           0 :         } else if (strcmp(tag, PCMK_VALUE_DENY) == 0) {
     181           0 :             acls = create_acl(child, acls, pcmk__xf_acl_deny);
     182             : 
     183             :         } else {
     184           0 :             crm_warn("Ignoring unknown ACL %s '%s'",
     185             :                      (kind? "kind" : "element"), tag);
     186             :         }
     187             :     }
     188             : 
     189           0 :     return acls;
     190             : }
     191             : 
     192             : /*
     193             :     <acls>
     194             :       <acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
     195             :       <acl_role id="auto-l33t-haxor">
     196             :         <acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
     197             :       </acl_role>
     198             :       <acl_target id="niceguy">
     199             :         <role id="observer"/>
     200             :       </acl_target>
     201             :       <acl_role id="observer">
     202             :         <acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
     203             :         <acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
     204             :         <acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
     205             :       </acl_role>
     206             :       <acl_target id="badidea"><role id="auto-badidea"/></acl_target>
     207             :       <acl_role id="auto-badidea">
     208             :         <acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
     209             :         <acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
     210             :       </acl_role>
     211             :     </acls>
     212             : */
     213             : 
     214             : static const char *
     215           0 : acl_to_text(enum xml_private_flags flags)
     216             : {
     217           0 :     if (pcmk_is_set(flags, pcmk__xf_acl_deny)) {
     218           0 :         return "deny";
     219             : 
     220           0 :     } else if (pcmk_any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_acl_create)) {
     221           0 :         return "read/write";
     222             : 
     223           0 :     } else if (pcmk_is_set(flags, pcmk__xf_acl_read)) {
     224           0 :         return "read";
     225             :     }
     226           0 :     return "none";
     227             : }
     228             : 
     229             : void
     230           0 : pcmk__apply_acl(xmlNode *xml)
     231             : {
     232           0 :     GList *aIter = NULL;
     233           0 :     xml_doc_private_t *docpriv = xml->doc->_private;
     234             :     xml_node_private_t *nodepriv;
     235           0 :     xmlXPathObjectPtr xpathObj = NULL;
     236             : 
     237           0 :     if (!xml_acl_enabled(xml)) {
     238           0 :         crm_trace("Skipping ACLs for user '%s' because not enabled for this XML",
     239             :                   docpriv->user);
     240           0 :         return;
     241             :     }
     242             : 
     243           0 :     for (aIter = docpriv->acls; aIter != NULL; aIter = aIter->next) {
     244           0 :         int max = 0, lpc = 0;
     245           0 :         xml_acl_t *acl = aIter->data;
     246             : 
     247           0 :         xpathObj = xpath_search(xml, acl->xpath);
     248           0 :         max = numXpathResults(xpathObj);
     249             : 
     250           0 :         for (lpc = 0; lpc < max; lpc++) {
     251           0 :             xmlNode *match = getXpathResult(xpathObj, lpc);
     252             : 
     253           0 :             nodepriv = match->_private;
     254           0 :             pcmk__set_xml_flags(nodepriv, acl->mode);
     255             : 
     256             :             // Build a GString only if tracing is enabled
     257           0 :             pcmk__if_tracing(
     258             :                 {
     259             :                     GString *path = pcmk__element_xpath(match);
     260             :                     crm_trace("Applying %s ACL to %s matched by %s",
     261             :                               acl_to_text(acl->mode), path->str, acl->xpath);
     262             :                     g_string_free(path, TRUE);
     263             :                 },
     264             :                 {}
     265             :             );
     266             :         }
     267           0 :         crm_trace("Applied %s ACL %s (%d match%s)",
     268             :                   acl_to_text(acl->mode), acl->xpath, max,
     269             :                   ((max == 1)? "" : "es"));
     270           0 :         freeXpathObject(xpathObj);
     271             :     }
     272             : }
     273             : 
     274             : /*!
     275             :  * \internal
     276             :  * \brief Unpack ACLs for a given user into the
     277             :  * metadata of the target XML tree
     278             :  *
     279             :  * Taking the description of ACLs from the source XML tree and
     280             :  * marking up the target XML tree with access information for the
     281             :  * given user by tacking it onto the relevant nodes
     282             :  *
     283             :  * \param[in]     source  XML with ACL definitions
     284             :  * \param[in,out] target  XML that ACLs will be applied to
     285             :  * \param[in]     user    Username whose ACLs need to be unpacked
     286             :  */
     287             : void
     288           0 : pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
     289             : {
     290           0 :     xml_doc_private_t *docpriv = NULL;
     291             : 
     292           0 :     if ((target == NULL) || (target->doc == NULL)
     293           0 :         || (target->doc->_private == NULL)) {
     294           0 :         return;
     295             :     }
     296             : 
     297           0 :     docpriv = target->doc->_private;
     298           0 :     if (!pcmk_acl_required(user)) {
     299           0 :         crm_trace("Not unpacking ACLs because not required for user '%s'",
     300             :                   user);
     301             : 
     302           0 :     } else if (docpriv->acls == NULL) {
     303           0 :         xmlNode *acls = get_xpath_object("//" PCMK_XE_ACLS, source, LOG_NEVER);
     304             : 
     305           0 :         pcmk__str_update(&docpriv->user, user);
     306             : 
     307           0 :         if (acls) {
     308           0 :             xmlNode *child = NULL;
     309             : 
     310           0 :             for (child = pcmk__xe_first_child(acls, NULL, NULL, NULL);
     311           0 :                  child != NULL; child = pcmk__xe_next(child)) {
     312             : 
     313             :                 /* @COMPAT PCMK__XE_ACL_USER was deprecated in Pacemaker 1.1.12
     314             :                  * (needed for rolling upgrades)
     315             :                  */
     316           0 :                 if (pcmk__xe_is(child, PCMK_XE_ACL_TARGET)
     317           0 :                     || pcmk__xe_is(child, PCMK__XE_ACL_USER)) {
     318           0 :                     const char *id = crm_element_value(child, PCMK_XA_NAME);
     319             : 
     320           0 :                     if (id == NULL) {
     321           0 :                         id = crm_element_value(child, PCMK_XA_ID);
     322             :                     }
     323             : 
     324           0 :                     if (id && strcmp(id, user) == 0) {
     325           0 :                         crm_debug("Unpacking ACLs for user '%s'", id);
     326           0 :                         docpriv->acls = parse_acl_entry(acls, child, docpriv->acls);
     327             :                     }
     328           0 :                 } else if (pcmk__xe_is(child, PCMK_XE_ACL_GROUP)) {
     329           0 :                     const char *id = crm_element_value(child, PCMK_XA_NAME);
     330             : 
     331           0 :                     if (id == NULL) {
     332           0 :                         id = crm_element_value(child, PCMK_XA_ID);
     333             :                     }
     334             : 
     335           0 :                     if (id && pcmk__is_user_in_group(user,id)) {
     336           0 :                         crm_debug("Unpacking ACLs for group '%s'", id);
     337           0 :                         docpriv->acls = parse_acl_entry(acls, child, docpriv->acls);
     338             :                     }
     339             :                 }
     340             :             }
     341             :         }
     342             :     }
     343             : }
     344             : 
     345             : /*!
     346             :  * \internal
     347             :  * \brief Copy source to target and set xf_acl_enabled flag in target
     348             :  *
     349             :  * \param[in]     acl_source    XML with ACL definitions
     350             :  * \param[in,out] target        XML that ACLs will be applied to
     351             :  * \param[in]     user          Username whose ACLs need to be set
     352             :  */
     353             : void
     354           0 : pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
     355             : {
     356           0 :     pcmk__unpack_acl(acl_source, target, user);
     357           0 :     pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled);
     358           0 :     pcmk__apply_acl(target);
     359           0 : }
     360             : 
     361             : static inline bool
     362           0 : test_acl_mode(enum xml_private_flags allowed, enum xml_private_flags requested)
     363             : {
     364           0 :     if (pcmk_is_set(allowed, pcmk__xf_acl_deny)) {
     365           0 :         return false;
     366             : 
     367           0 :     } else if (pcmk_all_flags_set(allowed, requested)) {
     368           0 :         return true;
     369             : 
     370           0 :     } else if (pcmk_is_set(requested, pcmk__xf_acl_read)
     371           0 :                && pcmk_is_set(allowed, pcmk__xf_acl_write)) {
     372           0 :         return true;
     373             : 
     374           0 :     } else if (pcmk_is_set(requested, pcmk__xf_acl_create)
     375           0 :                && pcmk_any_flags_set(allowed, pcmk__xf_acl_write|pcmk__xf_created)) {
     376           0 :         return true;
     377             :     }
     378           0 :     return false;
     379             : }
     380             : 
     381             : /*!
     382             :  * \internal
     383             :  * \brief Rid XML tree of all unreadable nodes and node properties
     384             :  *
     385             :  * \param[in,out] xml   Root XML node to be purged of attributes
     386             :  *
     387             :  * \return true if this node or any of its children are readable
     388             :  *         if false is returned, xml will be freed
     389             :  *
     390             :  * \note This function is recursive
     391             :  */
     392             : static bool
     393           0 : purge_xml_attributes(xmlNode *xml)
     394             : {
     395           0 :     xmlNode *child = NULL;
     396           0 :     xmlAttr *xIter = NULL;
     397           0 :     bool readable_children = false;
     398           0 :     xml_node_private_t *nodepriv = xml->_private;
     399             : 
     400           0 :     if (test_acl_mode(nodepriv->flags, pcmk__xf_acl_read)) {
     401           0 :         crm_trace("%s[@" PCMK_XA_ID "=%s] is readable",
     402             :                   xml->name, pcmk__xe_id(xml));
     403           0 :         return true;
     404             :     }
     405             : 
     406           0 :     xIter = xml->properties;
     407           0 :     while (xIter != NULL) {
     408           0 :         xmlAttr *tmp = xIter;
     409           0 :         const char *prop_name = (const char *)xIter->name;
     410             : 
     411           0 :         xIter = xIter->next;
     412           0 :         if (strcmp(prop_name, PCMK_XA_ID) == 0) {
     413           0 :             continue;
     414             :         }
     415             : 
     416           0 :         xmlUnsetProp(xml, tmp->name);
     417             :     }
     418             : 
     419           0 :     child = pcmk__xml_first_child(xml);
     420           0 :     while ( child != NULL ) {
     421           0 :         xmlNode *tmp = child;
     422             : 
     423           0 :         child = pcmk__xml_next(child);
     424           0 :         readable_children |= purge_xml_attributes(tmp);
     425             :     }
     426             : 
     427           0 :     if (!readable_children) {
     428           0 :         free_xml(xml); /* Nothing readable under here, purge completely */
     429             :     }
     430           0 :     return readable_children;
     431             : }
     432             : 
     433             : /*!
     434             :  * \brief Copy ACL-allowed portions of specified XML
     435             :  *
     436             :  * \param[in]  user        Username whose ACLs should be used
     437             :  * \param[in]  acl_source  XML containing ACLs
     438             :  * \param[in]  xml         XML to be copied
     439             :  * \param[out] result      Copy of XML portions readable via ACLs
     440             :  *
     441             :  * \return true if xml exists and ACLs are required for user, false otherwise
     442             :  * \note If this returns true, caller should use \p result rather than \p xml
     443             :  */
     444             : bool
     445           0 : xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
     446             :                       xmlNode **result)
     447             : {
     448           0 :     GList *aIter = NULL;
     449           0 :     xmlNode *target = NULL;
     450           0 :     xml_doc_private_t *docpriv = NULL;
     451             : 
     452           0 :     *result = NULL;
     453           0 :     if ((xml == NULL) || !pcmk_acl_required(user)) {
     454           0 :         crm_trace("Not filtering XML because ACLs not required for user '%s'",
     455             :                   user);
     456           0 :         return false;
     457             :     }
     458             : 
     459           0 :     crm_trace("Filtering XML copy using user '%s' ACLs", user);
     460           0 :     target = pcmk__xml_copy(NULL, xml);
     461           0 :     if (target == NULL) {
     462           0 :         return true;
     463             :     }
     464             : 
     465           0 :     pcmk__enable_acl(acl_source, target, user);
     466             : 
     467           0 :     docpriv = target->doc->_private;
     468           0 :     for(aIter = docpriv->acls; aIter != NULL && target; aIter = aIter->next) {
     469           0 :         int max = 0;
     470           0 :         xml_acl_t *acl = aIter->data;
     471             : 
     472           0 :         if (acl->mode != pcmk__xf_acl_deny) {
     473             :             /* Nothing to do */
     474             : 
     475           0 :         } else if (acl->xpath) {
     476           0 :             int lpc = 0;
     477           0 :             xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
     478             : 
     479           0 :             max = numXpathResults(xpathObj);
     480           0 :             for(lpc = 0; lpc < max; lpc++) {
     481           0 :                 xmlNode *match = getXpathResult(xpathObj, lpc);
     482             : 
     483           0 :                 if (!purge_xml_attributes(match) && (match == target)) {
     484           0 :                     crm_trace("ACLs deny user '%s' access to entire XML document",
     485             :                               user);
     486           0 :                     freeXpathObject(xpathObj);
     487           0 :                     return true;
     488             :                 }
     489             :             }
     490           0 :             crm_trace("ACLs deny user '%s' access to %s (%d %s)",
     491             :                       user, acl->xpath, max,
     492             :                       pcmk__plural_alt(max, "match", "matches"));
     493           0 :             freeXpathObject(xpathObj);
     494             :         }
     495             :     }
     496             : 
     497           0 :     if (!purge_xml_attributes(target)) {
     498           0 :         crm_trace("ACLs deny user '%s' access to entire XML document", user);
     499           0 :         return true;
     500             :     }
     501             : 
     502           0 :     if (docpriv->acls) {
     503           0 :         g_list_free_full(docpriv->acls, free_acl);
     504           0 :         docpriv->acls = NULL;
     505             : 
     506             :     } else {
     507           0 :         crm_trace("User '%s' without ACLs denied access to entire XML document",
     508             :                   user);
     509           0 :         free_xml(target);
     510           0 :         target = NULL;
     511             :     }
     512             : 
     513           0 :     if (target) {
     514           0 :         *result = target;
     515             :     }
     516             : 
     517           0 :     return true;
     518             : }
     519             : 
     520             : /*!
     521             :  * \internal
     522             :  * \brief Check whether creation of an XML element is implicitly allowed
     523             :  *
     524             :  * Check whether XML is a "scaffolding" element whose creation is implicitly
     525             :  * allowed regardless of ACLs (that is, it is not in the ACL section and has
     526             :  * no attributes other than \c PCMK_XA_ID).
     527             :  *
     528             :  * \param[in] xml  XML element to check
     529             :  *
     530             :  * \return true if XML element is implicitly allowed, false otherwise
     531             :  */
     532             : static bool
     533           0 : implicitly_allowed(const xmlNode *xml)
     534             : {
     535           0 :     GString *path = NULL;
     536             : 
     537           0 :     for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
     538           0 :         if (strcmp((const char *) prop->name, PCMK_XA_ID) != 0) {
     539           0 :             return false;
     540             :         }
     541             :     }
     542             : 
     543           0 :     path = pcmk__element_xpath(xml);
     544           0 :     CRM_ASSERT(path != NULL);
     545             : 
     546           0 :     if (strstr((const char *) path->str, "/" PCMK_XE_ACLS "/") != NULL) {
     547           0 :         g_string_free(path, TRUE);
     548           0 :         return false;
     549             :     }
     550             : 
     551           0 :     g_string_free(path, TRUE);
     552           0 :     return true;
     553             : }
     554             : 
     555             : #define display_id(xml) pcmk__s(pcmk__xe_id(xml), "<unset>")
     556             : 
     557             : /*!
     558             :  * \internal
     559             :  * \brief Drop XML nodes created in violation of ACLs
     560             :  *
     561             :  * Given an XML element, free all of its descendant nodes created in violation
     562             :  * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
     563             :  * that aren't in the ACL section and don't have any attributes other than
     564             :  * \c PCMK_XA_ID).
     565             :  *
     566             :  * \param[in,out] xml        XML to check
     567             :  * \param[in]     check_top  Whether to apply checks to argument itself
     568             :  *                           (if true, xml might get freed)
     569             :  *
     570             :  * \note This function is recursive
     571             :  */
     572             : void
     573           0 : pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
     574             : {
     575           0 :     xml_node_private_t *nodepriv = xml->_private;
     576             : 
     577           0 :     if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
     578           0 :         if (implicitly_allowed(xml)) {
     579           0 :             crm_trace("Creation of <%s> scaffolding with " PCMK_XA_ID "=\"%s\""
     580             :                       " is implicitly allowed",
     581             :                       xml->name, display_id(xml));
     582             : 
     583           0 :         } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
     584           0 :             crm_trace("ACLs allow creation of <%s> with " PCMK_XA_ID "=\"%s\"",
     585             :                       xml->name, display_id(xml));
     586             : 
     587           0 :         } else if (check_top) {
     588           0 :             crm_trace("ACLs disallow creation of <%s> with "
     589             :                       PCMK_XA_ID "=\"%s\"", xml->name, display_id(xml));
     590           0 :             pcmk_free_xml_subtree(xml);
     591           0 :             return;
     592             : 
     593             :         } else {
     594           0 :             crm_notice("ACLs would disallow creation of %s<%s> with "
     595             :                        PCMK_XA_ID "=\"%s\"",
     596             :                        ((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""),
     597             :                        xml->name, display_id(xml));
     598             :         }
     599             :     }
     600             : 
     601           0 :     for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
     602           0 :         xmlNode *child = cIter;
     603           0 :         cIter = pcmk__xml_next(cIter); /* In case it is free'd */
     604           0 :         pcmk__apply_creation_acl(child, true);
     605             :     }
     606             : }
     607             : 
     608             : /*!
     609             :  * \brief Check whether or not an XML node is ACL-denied
     610             :  *
     611             :  * \param[in]  xml node to check
     612             :  *
     613             :  * \return true if XML node exists and is ACL-denied, false otherwise
     614             :  */
     615             : bool
     616          21 : xml_acl_denied(const xmlNode *xml)
     617             : {
     618          21 :     if (xml && xml->doc && xml->doc->_private){
     619          18 :         xml_doc_private_t *docpriv = xml->doc->_private;
     620             : 
     621          18 :         return pcmk_is_set(docpriv->flags, pcmk__xf_acl_denied);
     622             :     }
     623           3 :     return false;
     624             : }
     625             : 
     626             : void
     627           0 : xml_acl_disable(xmlNode *xml)
     628             : {
     629           0 :     if (xml_acl_enabled(xml)) {
     630           0 :         xml_doc_private_t *docpriv = xml->doc->_private;
     631             : 
     632             :         /* Catch anything that was created but shouldn't have been */
     633           0 :         pcmk__apply_acl(xml);
     634           0 :         pcmk__apply_creation_acl(xml, false);
     635           0 :         pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
     636             :     }
     637           0 : }
     638             : 
     639             : /*!
     640             :  * \brief Check whether or not an XML node is ACL-enabled
     641             :  *
     642             :  * \param[in]  xml node to check
     643             :  *
     644             :  * \return true if XML node exists and is ACL-enabled, false otherwise
     645             :  */
     646             : bool
     647          64 : xml_acl_enabled(const xmlNode *xml)
     648             : {
     649          64 :     if (xml && xml->doc && xml->doc->_private){
     650          61 :         xml_doc_private_t *docpriv = xml->doc->_private;
     651             : 
     652          61 :         return pcmk_is_set(docpriv->flags, pcmk__xf_acl_enabled);
     653             :     }
     654           3 :     return false;
     655             : }
     656             : 
     657             : bool
     658           0 : pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
     659             : {
     660           0 :     CRM_ASSERT(xml);
     661           0 :     CRM_ASSERT(xml->doc);
     662           0 :     CRM_ASSERT(xml->doc->_private);
     663             : 
     664           0 :     if (pcmk__tracking_xml_changes(xml, false) && xml_acl_enabled(xml)) {
     665           0 :         xmlNode *parent = xml;
     666           0 :         xml_doc_private_t *docpriv = xml->doc->_private;
     667           0 :         GString *xpath = NULL;
     668             : 
     669           0 :         if (docpriv->acls == NULL) {
     670           0 :             pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
     671             : 
     672           0 :             pcmk__if_tracing({}, return false);
     673           0 :             xpath = pcmk__element_xpath(xml);
     674           0 :             if (name != NULL) {
     675           0 :                 pcmk__g_strcat(xpath, "[@", name, "]", NULL);
     676             :             }
     677             : 
     678           0 :             qb_log_from_external_source(__func__, __FILE__,
     679             :                                         "User '%s' without ACLs denied %s "
     680             :                                         "access to %s", LOG_TRACE, __LINE__, 0,
     681             :                                         docpriv->user, acl_to_text(mode),
     682           0 :                                         (const char *) xpath->str);
     683           0 :             g_string_free(xpath, TRUE);
     684           0 :             return false;
     685             :         }
     686             : 
     687             :         /* Walk the tree upwards looking for xml_acl_* flags
     688             :          * - Creating an attribute requires write permissions for the node
     689             :          * - Creating a child requires write permissions for the parent
     690             :          */
     691             : 
     692           0 :         if (name) {
     693           0 :             xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
     694             : 
     695           0 :             if (attr && mode == pcmk__xf_acl_create) {
     696           0 :                 mode = pcmk__xf_acl_write;
     697             :             }
     698             :         }
     699             : 
     700           0 :         while (parent && parent->_private) {
     701           0 :             xml_node_private_t *nodepriv = parent->_private;
     702           0 :             if (test_acl_mode(nodepriv->flags, mode)) {
     703           0 :                 return true;
     704             : 
     705           0 :             } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
     706           0 :                 pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
     707             : 
     708           0 :                 pcmk__if_tracing({}, return false);
     709           0 :                 xpath = pcmk__element_xpath(xml);
     710           0 :                 if (name != NULL) {
     711           0 :                     pcmk__g_strcat(xpath, "[@", name, "]", NULL);
     712             :                 }
     713             : 
     714           0 :                 qb_log_from_external_source(__func__, __FILE__,
     715             :                                             "%sACL denies user '%s' %s access "
     716             :                                             "to %s", LOG_TRACE, __LINE__, 0,
     717             :                                             (parent != xml)? "Parent ": "",
     718             :                                             docpriv->user, acl_to_text(mode),
     719           0 :                                             (const char *) xpath->str);
     720           0 :                 g_string_free(xpath, TRUE);
     721           0 :                 return false;
     722             :             }
     723           0 :             parent = parent->parent;
     724             :         }
     725             : 
     726           0 :         pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
     727             : 
     728           0 :         pcmk__if_tracing({}, return false);
     729           0 :         xpath = pcmk__element_xpath(xml);
     730           0 :         if (name != NULL) {
     731           0 :             pcmk__g_strcat(xpath, "[@", name, "]", NULL);
     732             :         }
     733             : 
     734           0 :         qb_log_from_external_source(__func__, __FILE__,
     735             :                                     "Default ACL denies user '%s' %s access to "
     736             :                                     "%s", LOG_TRACE, __LINE__, 0,
     737             :                                     docpriv->user, acl_to_text(mode),
     738           0 :                                     (const char *) xpath->str);
     739           0 :         g_string_free(xpath, TRUE);
     740           0 :         return false;
     741             :     }
     742             : 
     743           0 :     return true;
     744             : }
     745             : 
     746             : /*!
     747             :  * \brief Check whether ACLs are required for a given user
     748             :  *
     749             :  * \param[in]  User name to check
     750             :  *
     751             :  * \return true if the user requires ACLs, false otherwise
     752             :  */
     753             : bool
     754          98 : pcmk_acl_required(const char *user)
     755             : {
     756          98 :     if (pcmk__str_empty(user)) {
     757          95 :         crm_trace("ACLs not required because no user set");
     758          95 :         return false;
     759             : 
     760           3 :     } else if (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")) {
     761           2 :         crm_trace("ACLs not required for privileged user %s", user);
     762           2 :         return false;
     763             :     }
     764           1 :     crm_trace("ACLs required for %s", user);
     765           1 :     return true;
     766             : }
     767             : 
     768             : char *
     769           0 : pcmk__uid2username(uid_t uid)
     770             : {
     771           0 :     struct passwd *pwent = getpwuid(uid);
     772             : 
     773           0 :     if (pwent == NULL) {
     774           0 :         crm_perror(LOG_INFO, "Cannot get user details for user ID %d", uid);
     775           0 :         return NULL;
     776             :     }
     777           0 :     return pcmk__str_copy(pwent->pw_name);
     778             : }
     779             : 
     780             : /*!
     781             :  * \internal
     782             :  * \brief Set the ACL user field properly on an XML request
     783             :  *
     784             :  * Multiple user names are potentially involved in an XML request: the effective
     785             :  * user of the current process; the user name known from an IPC client
     786             :  * connection; and the user name obtained from the request itself, whether by
     787             :  * the current standard XML attribute name or an older legacy attribute name.
     788             :  * This function chooses the appropriate one that should be used for ACLs, sets
     789             :  * it in the request (using the standard attribute name, and the legacy name if
     790             :  * given), and returns it.
     791             :  *
     792             :  * \param[in,out] request    XML request to update
     793             :  * \param[in]     field      Alternate name for ACL user name XML attribute
     794             :  * \param[in]     peer_user  User name as known from IPC connection
     795             :  *
     796             :  * \return ACL user name actually used
     797             :  */
     798             : const char *
     799           0 : pcmk__update_acl_user(xmlNode *request, const char *field,
     800             :                       const char *peer_user)
     801             : {
     802             :     static const char *effective_user = NULL;
     803           0 :     const char *requested_user = NULL;
     804           0 :     const char *user = NULL;
     805             : 
     806           0 :     if (effective_user == NULL) {
     807           0 :         effective_user = pcmk__uid2username(geteuid());
     808           0 :         if (effective_user == NULL) {
     809           0 :             effective_user = pcmk__str_copy("#unprivileged");
     810           0 :             crm_err("Unable to determine effective user, assuming unprivileged for ACLs");
     811             :         }
     812             :     }
     813             : 
     814           0 :     requested_user = crm_element_value(request, PCMK_XE_ACL_TARGET);
     815           0 :     if (requested_user == NULL) {
     816             :         /* @COMPAT rolling upgrades <=1.1.11
     817             :          *
     818             :          * field is checked for backward compatibility with older versions that
     819             :          * did not use PCMK_XE_ACL_TARGET.
     820             :          */
     821           0 :         requested_user = crm_element_value(request, field);
     822             :     }
     823             : 
     824           0 :     if (!pcmk__is_privileged(effective_user)) {
     825             :         /* We're not running as a privileged user, set or overwrite any existing
     826             :          * value for PCMK_XE_ACL_TARGET
     827             :          */
     828           0 :         user = effective_user;
     829             : 
     830           0 :     } else if (peer_user == NULL && requested_user == NULL) {
     831             :         /* No user known or requested, use 'effective_user' and make sure one is
     832             :          * set for the request
     833             :          */
     834           0 :         user = effective_user;
     835             : 
     836           0 :     } else if (peer_user == NULL) {
     837             :         /* No user known, trusting 'requested_user' */
     838           0 :         user = requested_user;
     839             : 
     840           0 :     } else if (!pcmk__is_privileged(peer_user)) {
     841             :         /* The peer is not a privileged user, set or overwrite any existing
     842             :          * value for PCMK_XE_ACL_TARGET
     843             :          */
     844           0 :         user = peer_user;
     845             : 
     846           0 :     } else if (requested_user == NULL) {
     847             :         /* Even if we're privileged, make sure there is always a value set */
     848           0 :         user = peer_user;
     849             : 
     850             :     } else {
     851             :         /* Legal delegation to 'requested_user' */
     852           0 :         user = requested_user;
     853             :     }
     854             : 
     855             :     // This requires pointer comparison, not string comparison
     856           0 :     if (user != crm_element_value(request, PCMK_XE_ACL_TARGET)) {
     857           0 :         crm_xml_add(request, PCMK_XE_ACL_TARGET, user);
     858             :     }
     859             : 
     860           0 :     if (field != NULL && user != crm_element_value(request, field)) {
     861           0 :         crm_xml_add(request, field, user);
     862             :     }
     863             : 
     864           0 :     return requested_user;
     865             : }

Generated by: LCOV version 1.14