pacemaker  2.1.4-dc6eb4362
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 
104 static int
105 pcmk__acl_annotate_permissions_recursive(xmlNode *xml_modify)
106 {
107 
108  static xmlNs *ns_recycle_writable = NULL,
109  *ns_recycle_readable = NULL,
110  *ns_recycle_denied = NULL;
111  static const xmlDoc *prev_doc = NULL;
112 
113  xmlNode *i_node = NULL;
114  const xmlChar *ns;
115  int ret = EINVAL; // nodes have not been processed yet
116 
117  if (prev_doc == NULL || prev_doc != xml_modify->doc) {
118  prev_doc = xml_modify->doc;
119  ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
120  }
121 
122  for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
123  switch (i_node->type) {
124  case XML_ELEMENT_NODE:
126 
127  if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
128  ns = NS_DENIED;
129  } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
130  ns = NS_READABLE;
131  } else {
132  ns = NS_WRITABLE;
133  }
134  pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied);
135  /* XXX recursion can be turned into plain iteration to save stack */
136  if (i_node->properties != NULL) {
137  /* this is not entirely clear, but relies on the very same
138  class-hierarchy emulation that libxml2 has firmly baked in
139  its API/ABI */
140  ret |= pcmk__acl_annotate_permissions_recursive((xmlNodePtr) i_node->properties);
141  }
142  if (i_node->children != NULL) {
143  ret |= pcmk__acl_annotate_permissions_recursive(i_node->children);
144  }
145  break;
146  case XML_ATTRIBUTE_NODE:
147  /* we can utilize that parent has already been assigned the ns */
148  if (!pcmk__check_acl(i_node->parent,
149  (const char *) i_node->name,
151  ns = NS_DENIED;
152  } else if (!pcmk__check_acl(i_node,
153  (const char *) i_node->name,
155  ns = NS_READABLE;
156  } else {
157  ns = NS_WRITABLE;
158  }
159  pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied);
160  break;
161  case XML_COMMENT_NODE:
162  /* we can utilize that parent has already been assigned the ns */
163  if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_read))
164  {
165  ns = NS_DENIED;
166  }
167  else if (!pcmk__check_acl(i_node->parent, (const char *) i_node->name, pcmk__xf_acl_write))
168  {
169  ns = NS_READABLE;
170  }
171  else
172  {
173  ns = NS_WRITABLE;
174  }
175  pcmk__acl_mark_node_with_namespace(i_node, ns, &ret, &ns_recycle_writable, &ns_recycle_readable, &ns_recycle_denied);
176  break;
177  default:
178  break;
179  }
180  }
181 
182  return ret;
183 }
184 
185 int
186 pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc,
187  xmlDoc **acl_evaled_doc)
188 {
189  int ret, version;
190  xmlNode *target, *comment;
191  const char *validation;
192 
193  CRM_CHECK(cred != NULL, return EINVAL);
194  CRM_CHECK(cib_doc != NULL, return EINVAL);
195  CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
196 
197  /* avoid trivial accidental XML injection */
198  if (strpbrk(cred, "<>&") != NULL) {
199  return EINVAL;
200  }
201 
202  if (!pcmk_acl_required(cred)) {
203  /* nothing to evaluate */
204  return pcmk_rc_already;
205  }
206 
207  validation = crm_element_value(xmlDocGetRootElement(cib_doc),
209  version = get_schema_version(validation);
212  }
213 
214  target = copy_xml(xmlDocGetRootElement(cib_doc));
215  if (target == NULL) {
216  return EINVAL;
217  }
218 
220 
221  ret = pcmk__acl_annotate_permissions_recursive(target);
222 
223  if (ret == pcmk_rc_ok) {
224  char* credentials = crm_strdup_printf("ACLs as evaluated for user %s", cred);
225  comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
226  free(credentials);
227  if (comment == NULL) {
228  xmlFreeNode(target);
229  return EINVAL;
230  }
231  xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
232  *acl_evaled_doc = target->doc;
233  return pcmk_rc_ok;
234  } else {
235  xmlFreeNode(target);
236  return ret; //for now, it should be some kind of error
237  }
238 }
239 
240 int
241 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
242  xmlChar **doc_txt_ptr)
243 {
244  xmlDoc *xslt_doc;
245  xsltStylesheet *xslt;
246  xsltTransformContext *xslt_ctxt;
247  xmlDoc *res;
248  char *sfile;
249  static const char *params_namespace[] = {
250  "accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
251  "accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
252  "accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
253  "accessrendercfg:c-reset", "",
254  "accessrender:extra-spacing", "no",
255  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
256  NULL
257  }, *params_useansi[] = {
258  /* start with hard-coded defaults, then adapt per the template ones */
259  "accessrendercfg:c-writable", "\x1b[32m",
260  "accessrendercfg:c-readable", "\x1b[34m",
261  "accessrendercfg:c-denied", "\x1b[31m",
262  "accessrendercfg:c-reset", "\x1b[0m",
263  "accessrender:extra-spacing", "no",
264  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
265  NULL
266  }, *params_noansi[] = {
267  "accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
268  "accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
269  "accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
270  "accessrendercfg:c-reset", "",
271  "accessrender:extra-spacing", "yes",
272  "accessrender:self-reproducing-prefix", "",
273  NULL
274  };
275  const char **params;
276  int ret;
277  xmlParserCtxtPtr parser_ctxt;
278 
279  /* unfortunately, the input (coming from CIB originally) was parsed with
280  blanks ignored, and since the output is a conversion of XML to text
281  format (we would be covered otherwise thanks to implicit
282  pretty-printing), we need to dump the tree to string output first,
283  only to subsequently reparse it -- this time with blanks honoured */
284  xmlChar *annotated_dump;
285  int dump_size;
286 
287  xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
288  res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
289  XML_PARSE_NONET);
290  CRM_ASSERT(res != NULL);
291  xmlFree(annotated_dump);
292  xmlFreeDoc(annotated_doc);
293  annotated_doc = res;
294 
296  "access-render-2");
297  parser_ctxt = xmlNewParserCtxt();
298 
299  CRM_ASSERT(sfile != NULL);
300  CRM_ASSERT(parser_ctxt != NULL);
301 
302  xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
303 
304  xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
305  if (xslt == NULL) {
306  crm_crit("Problem in parsing %s", sfile);
307  return EINVAL;
308  }
309  free(sfile);
310  sfile = NULL;
311  xmlFreeParserCtxt(parser_ctxt);
312 
313  xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
314  CRM_ASSERT(xslt_ctxt != NULL);
315 
316  if (how == pcmk__acl_render_text) {
317  params = params_noansi;
318  } else if (how == pcmk__acl_render_namespace) {
319  params = params_namespace;
320  } else {
321  params = params_useansi;
322  }
323 
324  xsltQuoteUserParams(xslt_ctxt, params);
325 
326  res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
327  NULL, NULL, xslt_ctxt);
328 
329  xmlFreeDoc(annotated_doc);
330  annotated_doc = NULL;
331  xsltFreeTransformContext(xslt_ctxt);
332  xslt_ctxt = NULL;
333 
334  if (how == pcmk__acl_render_color && params != params_useansi) {
335  char **param_i = (char **) params;
336  do {
337  free(*param_i);
338  } while (*param_i++ != NULL);
339  free(params);
340  }
341 
342  if (res == NULL) {
343  ret = EINVAL;
344  } else {
345  int doc_txt_len;
346  int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
347  xmlFreeDoc(res);
348  if (temp == 0) {
349  ret = pcmk_rc_ok;
350  } else {
351  ret = EINVAL;
352  }
353  }
354  xsltFreeStylesheet(xslt);
355  return ret;
356 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
pcmk__acl_render_how
Definition: pcmki_acl.h:20
A dumping ground.
#define crm_crit(fmt, args...)
Definition: logging.h:357
#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:1024
#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:117
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:186
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:830
#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:529
#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:241
const xmlChar * pcmkXmlStr
Definition: xml.h:52
const char * target
Definition: pcmk_fence.c:28
#define CRM_ASSERT(expr)
Definition: results.h:42
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:338
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:611
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2934
uint32_t version
Definition: remote.c:147
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:683