root/lib/pacemaker/pcmk_acl.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. pcmk__acl_mark_node_with_namespace
  2. annotate_with_siblings
  3. pcmk__acl_annotate_permissions
  4. pcmk__acl_evaled_render

   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 pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
     /* [previous][next][first][last][top][bottom][index][help] */
  56                                    xmlNs **ns_recycle_writable,
  57                                    xmlNs **ns_recycle_readable,
  58                                    xmlNs **ns_recycle_denied)
  59 {
  60     if (ns == NS_WRITABLE)
  61     {
  62         if (*ns_recycle_writable == NULL)
  63         {
  64             *ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
  65                                            NS_WRITABLE, ACL_NS_Q_WRITABLE);
  66         }
  67         xmlSetNs(i_node, *ns_recycle_writable);
  68         *ret = pcmk_rc_ok;
  69     }
  70     else if (ns == NS_READABLE)
  71     {
  72         if (*ns_recycle_readable == NULL)
  73         {
  74             *ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
  75                                            NS_READABLE, ACL_NS_Q_READABLE);
  76         }
  77         xmlSetNs(i_node, *ns_recycle_readable);
  78         *ret = pcmk_rc_ok;
  79     }
  80     else if (ns == NS_DENIED)
  81     {
  82         if (*ns_recycle_denied == NULL)
  83         {
  84             *ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
  85                                          NS_DENIED, ACL_NS_Q_DENIED);
  86         };
  87         xmlSetNs(i_node, *ns_recycle_denied);
  88         *ret = pcmk_rc_ok;
  89     }
  90 }
  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 annotate_with_siblings(xmlNode *xml_modify)
     /* [previous][next][first][last][top][bottom][index][help] */
 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     xmlNode *i_node = NULL;
 118     const xmlChar *ns;
 119     int ret = EINVAL; // nodes have not been processed yet
 120 
 121     if (prev_doc == NULL || prev_doc != xml_modify->doc) {
 122         prev_doc = xml_modify->doc;
 123         ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
 124     }
 125 
 126     for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
 127         switch (i_node->type) {
 128             case XML_ELEMENT_NODE:
 129                 pcmk__set_xml_doc_flag(i_node, pcmk__xf_tracking);
 130 
 131                 if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
 132                     ns = NS_DENIED;
 133                 } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
 134                     ns = NS_READABLE;
 135                 } else {
 136                     ns = NS_WRITABLE;
 137                 }
 138                 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                 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                     ret |= annotate_with_siblings((xmlNodePtr)
 149                                                   i_node->properties);
 150                 }
 151                 if (i_node->children != NULL) {
 152                     ret |= annotate_with_siblings(i_node->children);
 153                 }
 154                 break;
 155 
 156             case XML_ATTRIBUTE_NODE:
 157                 // We can utilize that parent has already been assigned the ns
 158                 if (!pcmk__check_acl(i_node->parent,
 159                                      (const char *) i_node->name,
 160                                      pcmk__xf_acl_read)) {
 161                     ns = NS_DENIED;
 162                 } else if (!pcmk__check_acl(i_node,
 163                                        (const char *) i_node->name,
 164                                        pcmk__xf_acl_write)) {
 165                     ns = NS_READABLE;
 166                 } else {
 167                     ns = NS_WRITABLE;
 168                 }
 169                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
 170                                                    &ns_recycle_writable,
 171                                                    &ns_recycle_readable,
 172                                                    &ns_recycle_denied);
 173                 break;
 174 
 175             case XML_COMMENT_NODE:
 176                 // We can utilize that parent has already been assigned the ns
 177                 if (!pcmk__check_acl(i_node->parent,
 178                                      (const char *) i_node->name,
 179                                      pcmk__xf_acl_read)) {
 180                     ns = NS_DENIED;
 181                 } else if (!pcmk__check_acl(i_node->parent,
 182                                             (const char *) i_node->name,
 183                                             pcmk__xf_acl_write)) {
 184                     ns = NS_READABLE;
 185                 } else {
 186                     ns = NS_WRITABLE;
 187                 }
 188                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
 189                                                    &ns_recycle_writable,
 190                                                    &ns_recycle_readable,
 191                                                    &ns_recycle_denied);
 192                 break;
 193 
 194             default:
 195                 break;
 196         }
 197     }
 198 
 199     return ret;
 200 }
 201 
 202 int
 203 pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
     /* [previous][next][first][last][top][bottom][index][help] */
 204                                xmlDoc **acl_evaled_doc)
 205 {
 206     int ret;
 207     xmlNode *target, *comment;
 208     const char *validation;
 209 
 210     CRM_CHECK(cred != NULL, return EINVAL);
 211     CRM_CHECK(cib_doc != NULL, return EINVAL);
 212     CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
 213 
 214     /* avoid trivial accidental XML injection */
 215     if (strpbrk(cred, "<>&") != NULL) {
 216         return EINVAL;
 217     }
 218 
 219     if (!pcmk_acl_required(cred)) {
 220         /* nothing to evaluate */
 221         return pcmk_rc_already;
 222     }
 223 
 224     // @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2
 225     validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc),
 226                                    PCMK_XA_VALIDATE_WITH);
 227 
 228     if (pcmk__cmp_schemas_by_name(PCMK__COMPAT_ACL_2_MIN_INCL,
 229                                   validation) > 0) {
 230         return pcmk_rc_schema_validation;
 231     }
 232 
 233     target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
 234     if (target == NULL) {
 235         return EINVAL;
 236     }
 237 
 238     pcmk__enable_acl(target, target, cred);
 239 
 240     ret = annotate_with_siblings(target);
 241 
 242     if (ret == pcmk_rc_ok) {
 243         char *credentials = crm_strdup_printf("ACLs as evaluated for user %s",
 244                                               cred);
 245 
 246         comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
 247         free(credentials);
 248         if (comment == NULL) {
 249             xmlFreeNode(target);
 250             return EINVAL;
 251         }
 252         xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
 253         *acl_evaled_doc = target->doc;
 254         return pcmk_rc_ok;
 255     } else {
 256         xmlFreeNode(target);
 257         return ret; //for now, it should be some kind of error
 258     }
 259 }
 260 
 261 int
 262 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
     /* [previous][next][first][last][top][bottom][index][help] */
 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 rc = pcmk_rc_ok;
 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     CRM_ASSERT(how != pcmk__acl_render_none);
 309 
 310     // Color is the default render mode for terminals; text is default otherwise
 311     if (how == pcmk__acl_render_default) {
 312         if (isatty(STDOUT_FILENO)) {
 313             how = pcmk__acl_render_color;
 314         } else {
 315             how = pcmk__acl_render_text;
 316         }
 317     }
 318 
 319     xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
 320     res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
 321                      XML_PARSE_NONET);
 322     CRM_ASSERT(res != NULL);
 323     xmlFree(annotated_dump);
 324     xmlFreeDoc(annotated_doc);
 325     annotated_doc = res;
 326 
 327     sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
 328                                     "access-render-2");
 329     parser_ctxt = xmlNewParserCtxt();
 330 
 331     CRM_ASSERT(sfile != NULL);
 332     pcmk__mem_assert(parser_ctxt);
 333 
 334     xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
 335 
 336     xslt = xsltParseStylesheetDoc(xslt_doc);  /* acquires xslt_doc! */
 337     if (xslt == NULL) {
 338         crm_crit("Problem in parsing %s", sfile);
 339         rc = EINVAL;
 340         goto done;
 341     }
 342     xmlFreeParserCtxt(parser_ctxt);
 343 
 344     xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
 345     pcmk__mem_assert(xslt_ctxt);
 346 
 347     switch (how) {
 348         case pcmk__acl_render_namespace:
 349             params = params_namespace;
 350             break;
 351         case pcmk__acl_render_text:
 352             params = params_noansi;
 353             break;
 354         default:
 355             /* pcmk__acl_render_color is the only remaining option.
 356              * The compiler complains about params possibly uninitialized if we
 357              * don't use default here.
 358              */
 359             params = params_useansi;
 360             break;
 361     }
 362 
 363     xsltQuoteUserParams(xslt_ctxt, params);
 364 
 365     res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
 366                                   NULL, NULL, xslt_ctxt);
 367 
 368     xmlFreeDoc(annotated_doc);
 369     annotated_doc = NULL;
 370     xsltFreeTransformContext(xslt_ctxt);
 371     xslt_ctxt = NULL;
 372 
 373     if (how == pcmk__acl_render_color && params != params_useansi) {
 374         char **param_i = (char **) params;
 375         do {
 376             free(*param_i);
 377         } while (*param_i++ != NULL);
 378         free(params);
 379     }
 380 
 381     if (res == NULL) {
 382         rc = EINVAL;
 383     } else {
 384         int doc_txt_len;
 385         int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
 386         xmlFreeDoc(res);
 387         if (temp != 0) {
 388             rc = EINVAL;
 389         }
 390     }
 391 
 392 done:
 393     if (xslt != NULL) {
 394         xsltFreeStylesheet(xslt);
 395     }
 396     free(sfile);
 397     return rc;
 398 }

/* [previous][next][first][last][top][bottom][index][help] */