pacemaker  2.1.5-b7adf64e51
Scalable High-Availability cluster resource manager
pcmk_acl.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2022 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>
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 
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)
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 
106 static int
107 pcmk__acl_annotate_permissions_recursive(xmlNode *xml_modify)
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:
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,
153  ns = NS_DENIED;
154  } else if (!pcmk__check_acl(i_node,
155  (const char *) i_node->name,
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, xmlDoc *cib_doc,
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  validation = crm_element_value(xmlDocGetRootElement(cib_doc),
211  version = get_schema_version(validation);
214  }
215 
216  target = copy_xml(xmlDocGetRootElement(cib_doc));
217  if (target == NULL) {
218  return EINVAL;
219  }
220 
222 
223  ret = pcmk__acl_annotate_permissions_recursive(target);
224 
225  if (ret == pcmk_rc_ok) {
226  char* credentials = crm_strdup_printf("ACLs as evaluated for user %s", cred);
227  comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
228  free(credentials);
229  if (comment == NULL) {
230  xmlFreeNode(target);
231  return EINVAL;
232  }
233  xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
234  *acl_evaled_doc = target->doc;
235  return pcmk_rc_ok;
236  } else {
237  xmlFreeNode(target);
238  return ret; //for now, it should be some kind of error
239  }
240 }
241 
242 int
243 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
244  xmlChar **doc_txt_ptr)
245 {
246  xmlDoc *xslt_doc;
247  xsltStylesheet *xslt;
248  xsltTransformContext *xslt_ctxt;
249  xmlDoc *res;
250  char *sfile;
251  static const char *params_namespace[] = {
252  "accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
253  "accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
254  "accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
255  "accessrendercfg:c-reset", "",
256  "accessrender:extra-spacing", "no",
257  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
258  NULL
259  }, *params_useansi[] = {
260  /* start with hard-coded defaults, then adapt per the template ones */
261  "accessrendercfg:c-writable", "\x1b[32m",
262  "accessrendercfg:c-readable", "\x1b[34m",
263  "accessrendercfg:c-denied", "\x1b[31m",
264  "accessrendercfg:c-reset", "\x1b[0m",
265  "accessrender:extra-spacing", "no",
266  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
267  NULL
268  }, *params_noansi[] = {
269  "accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
270  "accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
271  "accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
272  "accessrendercfg:c-reset", "",
273  "accessrender:extra-spacing", "yes",
274  "accessrender:self-reproducing-prefix", "",
275  NULL
276  };
277  const char **params;
278  int ret;
279  xmlParserCtxtPtr parser_ctxt;
280 
281  /* unfortunately, the input (coming from CIB originally) was parsed with
282  blanks ignored, and since the output is a conversion of XML to text
283  format (we would be covered otherwise thanks to implicit
284  pretty-printing), we need to dump the tree to string output first,
285  only to subsequently reparse it -- this time with blanks honoured */
286  xmlChar *annotated_dump;
287  int dump_size;
288 
289  xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
290  res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
291  XML_PARSE_NONET);
292  CRM_ASSERT(res != NULL);
293  xmlFree(annotated_dump);
294  xmlFreeDoc(annotated_doc);
295  annotated_doc = res;
296 
298  "access-render-2");
299  parser_ctxt = xmlNewParserCtxt();
300 
301  CRM_ASSERT(sfile != NULL);
302  CRM_ASSERT(parser_ctxt != NULL);
303 
304  xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
305 
306  xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
307  if (xslt == NULL) {
308  crm_crit("Problem in parsing %s", sfile);
309  return EINVAL;
310  }
311  free(sfile);
312  sfile = NULL;
313  xmlFreeParserCtxt(parser_ctxt);
314 
315  xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
316  CRM_ASSERT(xslt_ctxt != NULL);
317 
318  if (how == pcmk__acl_render_text) {
319  params = params_noansi;
320  } else if (how == pcmk__acl_render_namespace) {
321  params = params_namespace;
322  } else {
323  params = params_useansi;
324  }
325 
326  xsltQuoteUserParams(xslt_ctxt, params);
327 
328  res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
329  NULL, NULL, xslt_ctxt);
330 
331  xmlFreeDoc(annotated_doc);
332  annotated_doc = NULL;
333  xsltFreeTransformContext(xslt_ctxt);
334  xslt_ctxt = NULL;
335 
336  if (how == pcmk__acl_render_color && params != params_useansi) {
337  char **param_i = (char **) params;
338  do {
339  free(*param_i);
340  } while (*param_i++ != NULL);
341  free(params);
342  }
343 
344  if (res == NULL) {
345  ret = EINVAL;
346  } else {
347  int doc_txt_len;
348  int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
349  xmlFreeDoc(res);
350  if (temp == 0) {
351  ret = pcmk_rc_ok;
352  } else {
353  ret = EINVAL;
354  }
355  }
356  xsltFreeStylesheet(xslt);
357  return ret;
358 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:227
pcmk__acl_render_how
Definition: pcmki_acl.h:20
A dumping ground.
#define crm_crit(fmt, args...)
Definition: logging.h:358
#define ACL_NS_PREFIX
Definition: pcmk_acl.c:34
#define ACL_NS_Q_READABLE
Definition: pcmk_acl.c:37
#define ACL_NS_Q_DENIED
Definition: pcmk_acl.c:38
int get_schema_version(const char *name)
Definition: schemas.c:1033
#define ACL_NS_Q_PREFIX
Definition: pcmk_acl.c:35
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:108
int pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc, xmlDoc **acl_evaled_doc)
Mark CIB with namespace-encoded result of ACLs eval&#39;d per credential.
Definition: pcmk_acl.c:188
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:891
#define ACL_NS_Q_WRITABLE
Definition: pcmk_acl.c:36
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:517
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition: pcmki_acl.h:33
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
Wrappers for and extensions to libxml2.
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:120
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition: pcmk_acl.c:243
const xmlChar * pcmkXmlStr
Definition: xml.h:50
const char * target
Definition: pcmk_fence.c:29
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:353
#define CRM_ASSERT(expr)
Definition: results.h:42
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:655
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:3063
uint32_t version
Definition: remote.c:213
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:751