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 *content = crm_strdup_printf("ACLs as evaluated for user %s",
 244                                           cred);
 245 
 246         comment = pcmk__xc_create(target->doc, content);
 247         xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
 248         *acl_evaled_doc = target->doc;
 249         free(content);
 250 
 251     } else {
 252         free_xml(target);
 253     }
 254     return ret;
 255 }
 256 
 257 int
 258 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
     /* [previous][next][first][last][top][bottom][index][help] */
 259                         xmlChar **doc_txt_ptr)
 260 {
 261     xmlDoc *xslt_doc;
 262     xsltStylesheet *xslt;
 263     xsltTransformContext *xslt_ctxt;
 264     xmlDoc *res;
 265     char *sfile;
 266     static const char *params_namespace[] = {
 267         "accessrendercfg:c-writable",           ACL_NS_Q_PREFIX "writable:",
 268         "accessrendercfg:c-readable",           ACL_NS_Q_PREFIX "readable:",
 269         "accessrendercfg:c-denied",             ACL_NS_Q_PREFIX "denied:",
 270         "accessrendercfg:c-reset",              "",
 271         "accessrender:extra-spacing",           "no",
 272         "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
 273         NULL
 274     }, *params_useansi[] = {
 275         /* start with hard-coded defaults, then adapt per the template ones */
 276         "accessrendercfg:c-writable",           "\x1b[32m",
 277         "accessrendercfg:c-readable",           "\x1b[34m",
 278         "accessrendercfg:c-denied",             "\x1b[31m",
 279         "accessrendercfg:c-reset",              "\x1b[0m",
 280         "accessrender:extra-spacing",           "no",
 281         "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
 282         NULL
 283     }, *params_noansi[] = {
 284         "accessrendercfg:c-writable",           "vvv---[ WRITABLE ]---vvv",
 285         "accessrendercfg:c-readable",           "vvv---[ READABLE ]---vvv",
 286         "accessrendercfg:c-denied",             "vvv---[ ~DENIED~ ]---vvv",
 287         "accessrendercfg:c-reset",              "",
 288         "accessrender:extra-spacing",           "yes",
 289         "accessrender:self-reproducing-prefix", "",
 290         NULL
 291     };
 292     const char **params;
 293     int rc = pcmk_rc_ok;
 294     xmlParserCtxtPtr parser_ctxt;
 295 
 296     /* unfortunately, the input (coming from CIB originally) was parsed with
 297        blanks ignored, and since the output is a conversion of XML to text
 298        format (we would be covered otherwise thanks to implicit
 299        pretty-printing), we need to dump the tree to string output first,
 300        only to subsequently reparse it -- this time with blanks honoured */
 301     xmlChar *annotated_dump;
 302     int dump_size;
 303 
 304     pcmk__assert(how != pcmk__acl_render_none);
 305 
 306     // Color is the default render mode for terminals; text is default otherwise
 307     if (how == pcmk__acl_render_default) {
 308         if (isatty(STDOUT_FILENO)) {
 309             how = pcmk__acl_render_color;
 310         } else {
 311             how = pcmk__acl_render_text;
 312         }
 313     }
 314 
 315     xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
 316 
 317     /* res does not need private data: it's temporary and used only with libxslt
 318      * functions
 319      */
 320     res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
 321                      XML_PARSE_NONET);
 322     pcmk__assert(res != NULL);
 323     xmlFree(annotated_dump);
 324     pcmk__xml_free_doc(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     pcmk__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     pcmk__xml_free_doc(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 
 387         pcmk__xml_free_doc(res);
 388         if (temp != 0) {
 389             rc = EINVAL;
 390         }
 391     }
 392 
 393 done:
 394     if (xslt != NULL) {
 395         xsltFreeStylesheet(xslt);
 396     }
 397     free(sfile);
 398     return rc;
 399 }

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