LCOV - code coverage report
Current view: top level - pacemaker - pcmk_acl.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 146 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 4 0.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/parser.h>
      20             : #include <libxml/tree.h>
      21             : #include <libxml/xpath.h>
      22             : #include <libxslt/transform.h>
      23             : #include <libxslt/variables.h>
      24             : #include <libxslt/xsltutils.h>
      25             : 
      26             : #include <crm/crm.h>
      27             : #include <crm/common/xml.h>
      28             : #include <crm/common/xml_internal.h>
      29             : #include <crm/common/internal.h>
      30             : 
      31             : #include <pacemaker-internal.h>
      32             : 
      33             : #define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
      34             : #define ACL_NS_Q_PREFIX  "pcmk-access-"
      35             : #define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX   "writable"
      36             : #define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX   "readable"
      37             : #define ACL_NS_Q_DENIED   (const xmlChar *) ACL_NS_Q_PREFIX   "denied"
      38             : 
      39             : static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
      40             : static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
      41             : static const xmlChar *NS_DENIED =   (const xmlChar *) ACL_NS_PREFIX "denied";
      42             : 
      43             : /*!
      44             :  * \brief This function takes a node and marks it with the namespace
      45             :  *        given in the ns parameter.
      46             :  *
      47             :  * \param[in,out] i_node
      48             :  * \param[in] ns
      49             :  * \param[in,out] ret
      50             :  * \param[in,out] ns_recycle_writable
      51             :  * \param[in,out] ns_recycle_readable
      52             :  * \param[in,out] ns_recycle_denied
      53             :  */
      54             : static void
      55           0 : pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
      56             :                                    xmlNs **ns_recycle_writable,
      57             :                                    xmlNs **ns_recycle_readable,
      58             :                                    xmlNs **ns_recycle_denied)
      59             : {
      60           0 :     if (ns == NS_WRITABLE)
      61             :     {
      62           0 :         if (*ns_recycle_writable == NULL)
      63             :         {
      64           0 :             *ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
      65             :                                            NS_WRITABLE, ACL_NS_Q_WRITABLE);
      66             :         }
      67           0 :         xmlSetNs(i_node, *ns_recycle_writable);
      68           0 :         *ret = pcmk_rc_ok;
      69             :     }
      70           0 :     else if (ns == NS_READABLE)
      71             :     {
      72           0 :         if (*ns_recycle_readable == NULL)
      73             :         {
      74           0 :             *ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
      75             :                                            NS_READABLE, ACL_NS_Q_READABLE);
      76             :         }
      77           0 :         xmlSetNs(i_node, *ns_recycle_readable);
      78           0 :         *ret = pcmk_rc_ok;
      79             :     }
      80           0 :     else if (ns == NS_DENIED)
      81             :     {
      82           0 :         if (*ns_recycle_denied == NULL)
      83             :         {
      84           0 :             *ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
      85             :                                          NS_DENIED, ACL_NS_Q_DENIED);
      86             :         };
      87           0 :         xmlSetNs(i_node, *ns_recycle_denied);
      88           0 :         *ret = pcmk_rc_ok;
      89             :     }
      90           0 : }
      91             : 
      92             : /*!
      93             :  * \brief Annotate a given XML element or property and its siblings with
      94             :  *        XML namespaces to indicate ACL permissions
      95             :  *
      96             :  * \param[in,out] xml_modify  XML to annotate
      97             :  *
      98             :  * \return  A standard Pacemaker return code
      99             :  *          Namely:
     100             :  *          - pcmk_rc_ok upon success,
     101             :  *          - pcmk_rc_already if ACLs were not applicable,
     102             :  *          - pcmk_rc_schema_validation if the validation schema version
     103             :  *              is unsupported (see note), or
     104             :  *          - EINVAL or ENOMEM as appropriate;
     105             :  *
     106             :  * \note This function is recursive
     107             :  */
     108             : static int
     109           0 : annotate_with_siblings(xmlNode *xml_modify)
     110             : {
     111             : 
     112             :     static xmlNs *ns_recycle_writable = NULL,
     113             :                  *ns_recycle_readable = NULL,
     114             :                  *ns_recycle_denied = NULL;
     115             :     static const xmlDoc *prev_doc = NULL;
     116             : 
     117           0 :     xmlNode *i_node = NULL;
     118             :     const xmlChar *ns;
     119           0 :     int ret = EINVAL; // nodes have not been processed yet
     120             : 
     121           0 :     if (prev_doc == NULL || prev_doc != xml_modify->doc) {
     122           0 :         prev_doc = xml_modify->doc;
     123           0 :         ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
     124             :     }
     125             : 
     126           0 :     for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
     127           0 :         switch (i_node->type) {
     128           0 :             case XML_ELEMENT_NODE:
     129           0 :                 pcmk__set_xml_doc_flag(i_node, pcmk__xf_tracking);
     130             : 
     131           0 :                 if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
     132           0 :                     ns = NS_DENIED;
     133           0 :                 } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
     134           0 :                     ns = NS_READABLE;
     135             :                 } else {
     136           0 :                     ns = NS_WRITABLE;
     137             :                 }
     138           0 :                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
     139             :                                                    &ns_recycle_writable,
     140             :                                                    &ns_recycle_readable,
     141             :                                                    &ns_recycle_denied);
     142             :                 // @TODO Could replace recursion with iteration to save stack
     143           0 :                 if (i_node->properties != NULL) {
     144             :                     /* This is not entirely clear, but relies on the very same
     145             :                      * class-hierarchy emulation that libxml2 has firmly baked
     146             :                      * in its API/ABI
     147             :                      */
     148           0 :                     ret |= annotate_with_siblings((xmlNodePtr)
     149           0 :                                                   i_node->properties);
     150             :                 }
     151           0 :                 if (i_node->children != NULL) {
     152           0 :                     ret |= annotate_with_siblings(i_node->children);
     153             :                 }
     154           0 :                 break;
     155             : 
     156           0 :             case XML_ATTRIBUTE_NODE:
     157             :                 // We can utilize that parent has already been assigned the ns
     158           0 :                 if (!pcmk__check_acl(i_node->parent,
     159           0 :                                      (const char *) i_node->name,
     160             :                                      pcmk__xf_acl_read)) {
     161           0 :                     ns = NS_DENIED;
     162           0 :                 } else if (!pcmk__check_acl(i_node,
     163           0 :                                        (const char *) i_node->name,
     164             :                                        pcmk__xf_acl_write)) {
     165           0 :                     ns = NS_READABLE;
     166             :                 } else {
     167           0 :                     ns = NS_WRITABLE;
     168             :                 }
     169           0 :                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
     170             :                                                    &ns_recycle_writable,
     171             :                                                    &ns_recycle_readable,
     172             :                                                    &ns_recycle_denied);
     173           0 :                 break;
     174             : 
     175           0 :             case XML_COMMENT_NODE:
     176             :                 // We can utilize that parent has already been assigned the ns
     177           0 :                 if (!pcmk__check_acl(i_node->parent,
     178           0 :                                      (const char *) i_node->name,
     179             :                                      pcmk__xf_acl_read)) {
     180           0 :                     ns = NS_DENIED;
     181           0 :                 } else if (!pcmk__check_acl(i_node->parent,
     182           0 :                                             (const char *) i_node->name,
     183             :                                             pcmk__xf_acl_write)) {
     184           0 :                     ns = NS_READABLE;
     185             :                 } else {
     186           0 :                     ns = NS_WRITABLE;
     187             :                 }
     188           0 :                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
     189             :                                                    &ns_recycle_writable,
     190             :                                                    &ns_recycle_readable,
     191             :                                                    &ns_recycle_denied);
     192           0 :                 break;
     193             : 
     194           0 :             default:
     195           0 :                 break;
     196             :         }
     197             :     }
     198             : 
     199           0 :     return ret;
     200             : }
     201             : 
     202             : int
     203           0 : pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
     204             :                                xmlDoc **acl_evaled_doc)
     205             : {
     206             :     int ret;
     207             :     xmlNode *target, *comment;
     208             :     const char *validation;
     209             : 
     210           0 :     CRM_CHECK(cred != NULL, return EINVAL);
     211           0 :     CRM_CHECK(cib_doc != NULL, return EINVAL);
     212           0 :     CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
     213             : 
     214             :     /* avoid trivial accidental XML injection */
     215           0 :     if (strpbrk(cred, "<>&") != NULL) {
     216           0 :         return EINVAL;
     217             :     }
     218             : 
     219           0 :     if (!pcmk_acl_required(cred)) {
     220             :         /* nothing to evaluate */
     221           0 :         return pcmk_rc_already;
     222             :     }
     223             : 
     224             :     // @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2
     225           0 :     validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc),
     226             :                                    PCMK_XA_VALIDATE_WITH);
     227             : 
     228           0 :     if (pcmk__cmp_schemas_by_name(PCMK__COMPAT_ACL_2_MIN_INCL,
     229             :                                   validation) > 0) {
     230           0 :         return pcmk_rc_schema_validation;
     231             :     }
     232             : 
     233           0 :     target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
     234           0 :     if (target == NULL) {
     235           0 :         return EINVAL;
     236             :     }
     237             : 
     238           0 :     pcmk__enable_acl(target, target, cred);
     239             : 
     240           0 :     ret = annotate_with_siblings(target);
     241             : 
     242           0 :     if (ret == pcmk_rc_ok) {
     243           0 :         char *credentials = crm_strdup_printf("ACLs as evaluated for user %s",
     244             :                                               cred);
     245             : 
     246           0 :         comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
     247           0 :         free(credentials);
     248           0 :         if (comment == NULL) {
     249           0 :             xmlFreeNode(target);
     250           0 :             return EINVAL;
     251             :         }
     252           0 :         xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
     253           0 :         *acl_evaled_doc = target->doc;
     254           0 :         return pcmk_rc_ok;
     255             :     } else {
     256           0 :         xmlFreeNode(target);
     257           0 :         return ret; //for now, it should be some kind of error
     258             :     }
     259             : }
     260             : 
     261             : int
     262           0 : pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
     263             :                         xmlChar **doc_txt_ptr)
     264             : {
     265             :     xmlDoc *xslt_doc;
     266             :     xsltStylesheet *xslt;
     267             :     xsltTransformContext *xslt_ctxt;
     268             :     xmlDoc *res;
     269             :     char *sfile;
     270             :     static const char *params_namespace[] = {
     271             :         "accessrendercfg:c-writable",           ACL_NS_Q_PREFIX "writable:",
     272             :         "accessrendercfg:c-readable",           ACL_NS_Q_PREFIX "readable:",
     273             :         "accessrendercfg:c-denied",             ACL_NS_Q_PREFIX "denied:",
     274             :         "accessrendercfg:c-reset",              "",
     275             :         "accessrender:extra-spacing",           "no",
     276             :         "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
     277             :         NULL
     278             :     }, *params_useansi[] = {
     279             :         /* start with hard-coded defaults, then adapt per the template ones */
     280             :         "accessrendercfg:c-writable",           "\x1b[32m",
     281             :         "accessrendercfg:c-readable",           "\x1b[34m",
     282             :         "accessrendercfg:c-denied",             "\x1b[31m",
     283             :         "accessrendercfg:c-reset",              "\x1b[0m",
     284             :         "accessrender:extra-spacing",           "no",
     285             :         "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
     286             :         NULL
     287             :     }, *params_noansi[] = {
     288             :         "accessrendercfg:c-writable",           "vvv---[ WRITABLE ]---vvv",
     289             :         "accessrendercfg:c-readable",           "vvv---[ READABLE ]---vvv",
     290             :         "accessrendercfg:c-denied",             "vvv---[ ~DENIED~ ]---vvv",
     291             :         "accessrendercfg:c-reset",              "",
     292             :         "accessrender:extra-spacing",           "yes",
     293             :         "accessrender:self-reproducing-prefix", "",
     294             :         NULL
     295             :     };
     296             :     const char **params;
     297             :     int ret;
     298             :     xmlParserCtxtPtr parser_ctxt;
     299             : 
     300             :     /* unfortunately, the input (coming from CIB originally) was parsed with
     301             :        blanks ignored, and since the output is a conversion of XML to text
     302             :        format (we would be covered otherwise thanks to implicit
     303             :        pretty-printing), we need to dump the tree to string output first,
     304             :        only to subsequently reparse it -- this time with blanks honoured */
     305             :     xmlChar *annotated_dump;
     306             :     int dump_size;
     307             : 
     308           0 :     CRM_ASSERT(how != pcmk__acl_render_none);
     309             : 
     310             :     // Color is the default render mode for terminals; text is default otherwise
     311           0 :     if (how == pcmk__acl_render_default) {
     312           0 :         if (isatty(STDOUT_FILENO)) {
     313           0 :             how = pcmk__acl_render_color;
     314             :         } else {
     315           0 :             how = pcmk__acl_render_text;
     316             :         }
     317             :     }
     318             : 
     319           0 :     xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
     320           0 :     res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
     321             :                      XML_PARSE_NONET);
     322           0 :     CRM_ASSERT(res != NULL);
     323           0 :     xmlFree(annotated_dump);
     324           0 :     xmlFreeDoc(annotated_doc);
     325           0 :     annotated_doc = res;
     326             : 
     327           0 :     sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
     328             :                                     "access-render-2");
     329           0 :     parser_ctxt = xmlNewParserCtxt();
     330             : 
     331           0 :     CRM_ASSERT(sfile != NULL);
     332           0 :     pcmk__mem_assert(parser_ctxt);
     333             : 
     334           0 :     xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
     335             : 
     336           0 :     xslt = xsltParseStylesheetDoc(xslt_doc);  /* acquires xslt_doc! */
     337           0 :     if (xslt == NULL) {
     338           0 :         crm_crit("Problem in parsing %s", sfile);
     339           0 :         return EINVAL;
     340             :     }
     341           0 :     free(sfile);
     342           0 :     sfile = NULL;
     343           0 :     xmlFreeParserCtxt(parser_ctxt);
     344             : 
     345           0 :     xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
     346           0 :     pcmk__mem_assert(xslt_ctxt);
     347             : 
     348           0 :     switch (how) {
     349           0 :         case pcmk__acl_render_namespace:
     350           0 :             params = params_namespace;
     351           0 :             break;
     352           0 :         case pcmk__acl_render_text:
     353           0 :             params = params_noansi;
     354           0 :             break;
     355           0 :         default:
     356             :             /* pcmk__acl_render_color is the only remaining option.
     357             :              * The compiler complains about params possibly uninitialized if we
     358             :              * don't use default here.
     359             :              */
     360           0 :             params = params_useansi;
     361           0 :             break;
     362             :     }
     363             : 
     364           0 :     xsltQuoteUserParams(xslt_ctxt, params);
     365             : 
     366           0 :     res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
     367             :                                   NULL, NULL, xslt_ctxt);
     368             : 
     369           0 :     xmlFreeDoc(annotated_doc);
     370           0 :     annotated_doc = NULL;
     371           0 :     xsltFreeTransformContext(xslt_ctxt);
     372           0 :     xslt_ctxt = NULL;
     373             : 
     374           0 :     if (how == pcmk__acl_render_color && params != params_useansi) {
     375           0 :         char **param_i = (char **) params;
     376             :         do {
     377           0 :             free(*param_i);
     378           0 :         } while (*param_i++ != NULL);
     379           0 :         free(params);
     380             :     }
     381             : 
     382           0 :     if (res == NULL) {
     383           0 :         ret = EINVAL;
     384             :     } else {
     385             :         int doc_txt_len;
     386           0 :         int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
     387           0 :         xmlFreeDoc(res);
     388           0 :         if (temp == 0) {
     389           0 :             ret = pcmk_rc_ok;
     390             :         } else {
     391           0 :             ret = EINVAL;
     392             :         }
     393             :     }
     394           0 :     xsltFreeStylesheet(xslt);
     395           0 :     return ret;
     396             : }

Generated by: LCOV version 1.14