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. pcmk__acl_annotate_permissions_recursive
  3. pcmk__acl_annotate_permissions
  4. pcmk__acl_evaled_render

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

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