diff --git a/lib/pacemaker/pcmk_acl.c b/lib/pacemaker/pcmk_acl.c index 9bd3d646d1..206c71e123 100644 --- a/lib/pacemaker/pcmk_acl.c +++ b/lib/pacemaker/pcmk_acl.c @@ -1,396 +1,398 @@ /* * Copyright 2004-2024 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/" #define ACL_NS_Q_PREFIX "pcmk-access-" #define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX "writable" #define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX "readable" #define ACL_NS_Q_DENIED (const xmlChar *) ACL_NS_Q_PREFIX "denied" static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable"; static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable"; static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied"; /*! * \brief This function takes a node and marks it with the namespace * given in the ns parameter. * * \param[in,out] i_node * \param[in] ns * \param[in,out] ret * \param[in,out] ns_recycle_writable * \param[in,out] ns_recycle_readable * \param[in,out] ns_recycle_denied */ static void 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) { if (ns == NS_WRITABLE) { if (*ns_recycle_writable == NULL) { *ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_WRITABLE, ACL_NS_Q_WRITABLE); } xmlSetNs(i_node, *ns_recycle_writable); *ret = pcmk_rc_ok; } else if (ns == NS_READABLE) { if (*ns_recycle_readable == NULL) { *ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_READABLE, ACL_NS_Q_READABLE); } xmlSetNs(i_node, *ns_recycle_readable); *ret = pcmk_rc_ok; } else if (ns == NS_DENIED) { if (*ns_recycle_denied == NULL) { *ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc), NS_DENIED, ACL_NS_Q_DENIED); }; xmlSetNs(i_node, *ns_recycle_denied); *ret = pcmk_rc_ok; } } /*! * \brief Annotate a given XML element or property and its siblings with * XML namespaces to indicate ACL permissions * * \param[in,out] xml_modify XML to annotate * * \return A standard Pacemaker return code * Namely: * - pcmk_rc_ok upon success, * - pcmk_rc_already if ACLs were not applicable, * - pcmk_rc_schema_validation if the validation schema version * is unsupported (see note), or * - EINVAL or ENOMEM as appropriate; * * \note This function is recursive */ static int annotate_with_siblings(xmlNode *xml_modify) { static xmlNs *ns_recycle_writable = NULL, *ns_recycle_readable = NULL, *ns_recycle_denied = NULL; static const xmlDoc *prev_doc = NULL; xmlNode *i_node = NULL; const xmlChar *ns; int ret = EINVAL; // nodes have not been processed yet if (prev_doc == NULL || prev_doc != xml_modify->doc) { prev_doc = xml_modify->doc; ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL; } for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) { switch (i_node->type) { case XML_ELEMENT_NODE: pcmk__set_xml_doc_flag(i_node, pcmk__xf_tracking); if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) { ns = NS_DENIED; } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) { ns = NS_READABLE; } else { ns = NS_WRITABLE; } pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied); // @TODO Could replace recursion with iteration to save stack if (i_node->properties != NULL) { /* This is not entirely clear, but relies on the very same * class-hierarchy emulation that libxml2 has firmly baked * in its API/ABI */ ret |= annotate_with_siblings((xmlNodePtr) i_node->properties); } if (i_node->children != NULL) { ret |= annotate_with_siblings(i_node->children); } break; case XML_ATTRIBUTE_NODE: // We can utilize that parent has already been assigned the ns if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_read)) { ns = NS_DENIED; } else if (!pcmk__check_acl(i_node, (const char *) i_node->name, pcmk__xf_acl_write)) { ns = NS_READABLE; } else { ns = NS_WRITABLE; } pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied); break; case XML_COMMENT_NODE: // We can utilize that parent has already been assigned the ns if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_read)) { ns = NS_DENIED; } else if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_write)) { ns = NS_READABLE; } else { ns = NS_WRITABLE; } pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied); break; default: break; } } return ret; } int pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc, xmlDoc **acl_evaled_doc) { int ret; xmlNode *target, *comment; const char *validation; CRM_CHECK(cred != NULL, return EINVAL); CRM_CHECK(cib_doc != NULL, return EINVAL); CRM_CHECK(acl_evaled_doc != NULL, return EINVAL); /* avoid trivial accidental XML injection */ if (strpbrk(cred, "<>&") != NULL) { return EINVAL; } if (!pcmk_acl_required(cred)) { /* nothing to evaluate */ return pcmk_rc_already; } // @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2 validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc), PCMK_XA_VALIDATE_WITH); if (pcmk__cmp_schemas_by_name(PCMK__COMPAT_ACL_2_MIN_INCL, validation) > 0) { return pcmk_rc_schema_validation; } target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc)); if (target == NULL) { return EINVAL; } pcmk__enable_acl(target, target, cred); ret = annotate_with_siblings(target); if (ret == pcmk_rc_ok) { char *credentials = crm_strdup_printf("ACLs as evaluated for user %s", cred); comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials); free(credentials); if (comment == NULL) { xmlFreeNode(target); return EINVAL; } xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment); *acl_evaled_doc = target->doc; return pcmk_rc_ok; } else { xmlFreeNode(target); return ret; //for now, it should be some kind of error } } int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr) { xmlDoc *xslt_doc; xsltStylesheet *xslt; xsltTransformContext *xslt_ctxt; xmlDoc *res; char *sfile; static const char *params_namespace[] = { "accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:", "accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:", "accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:", "accessrendercfg:c-reset", "", "accessrender:extra-spacing", "no", "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX, NULL }, *params_useansi[] = { /* start with hard-coded defaults, then adapt per the template ones */ "accessrendercfg:c-writable", "\x1b[32m", "accessrendercfg:c-readable", "\x1b[34m", "accessrendercfg:c-denied", "\x1b[31m", "accessrendercfg:c-reset", "\x1b[0m", "accessrender:extra-spacing", "no", "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX, NULL }, *params_noansi[] = { "accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv", "accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv", "accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv", "accessrendercfg:c-reset", "", "accessrender:extra-spacing", "yes", "accessrender:self-reproducing-prefix", "", NULL }; const char **params; - int ret; + int rc = pcmk_rc_ok; xmlParserCtxtPtr parser_ctxt; /* unfortunately, the input (coming from CIB originally) was parsed with blanks ignored, and since the output is a conversion of XML to text format (we would be covered otherwise thanks to implicit pretty-printing), we need to dump the tree to string output first, only to subsequently reparse it -- this time with blanks honoured */ xmlChar *annotated_dump; int dump_size; CRM_ASSERT(how != pcmk__acl_render_none); // Color is the default render mode for terminals; text is default otherwise if (how == pcmk__acl_render_default) { if (isatty(STDOUT_FILENO)) { how = pcmk__acl_render_color; } else { how = pcmk__acl_render_text; } } xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1); res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL, XML_PARSE_NONET); CRM_ASSERT(res != NULL); xmlFree(annotated_dump); xmlFreeDoc(annotated_doc); annotated_doc = res; sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt, "access-render-2"); parser_ctxt = xmlNewParserCtxt(); CRM_ASSERT(sfile != NULL); pcmk__mem_assert(parser_ctxt); xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET); xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */ if (xslt == NULL) { crm_crit("Problem in parsing %s", sfile); - return EINVAL; + rc = EINVAL; + goto done; } - free(sfile); - sfile = NULL; xmlFreeParserCtxt(parser_ctxt); xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc); pcmk__mem_assert(xslt_ctxt); switch (how) { case pcmk__acl_render_namespace: params = params_namespace; break; case pcmk__acl_render_text: params = params_noansi; break; default: /* pcmk__acl_render_color is the only remaining option. * The compiler complains about params possibly uninitialized if we * don't use default here. */ params = params_useansi; break; } xsltQuoteUserParams(xslt_ctxt, params); res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL, NULL, NULL, xslt_ctxt); xmlFreeDoc(annotated_doc); annotated_doc = NULL; xsltFreeTransformContext(xslt_ctxt); xslt_ctxt = NULL; if (how == pcmk__acl_render_color && params != params_useansi) { char **param_i = (char **) params; do { free(*param_i); } while (*param_i++ != NULL); free(params); } if (res == NULL) { - ret = EINVAL; + rc = EINVAL; } else { int doc_txt_len; int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt); xmlFreeDoc(res); - if (temp == 0) { - ret = pcmk_rc_ok; - } else { - ret = EINVAL; + if (temp != 0) { + rc = EINVAL; } } - xsltFreeStylesheet(xslt); - return ret; + +done: + if (xslt != NULL) { + xsltFreeStylesheet(xslt); + } + free(sfile); + return rc; }