pacemaker 2.1.8-2.1.8
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
pcmk_acl.c
Go to the documentation of this file.
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>
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
39static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
40static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
41static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied";
42
54static void
55pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
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
108static int
109annotate_with_siblings(xmlNode *xml_modify)
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:
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,
161 ns = NS_DENIED;
162 } else if (!pcmk__check_acl(i_node,
163 (const char *) i_node->name,
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,
180 ns = NS_DENIED;
181 } else if (!pcmk__check_acl(i_node->parent,
182 (const char *) i_node->name,
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
202int
203pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
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),
227
229 validation) > 0) {
231 }
232
233 target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
234 if (target == NULL) {
235 return EINVAL;
236 }
237
239
240 ret = annotate_with_siblings(target);
241
242 if (ret == pcmk_rc_ok) {
243 char *credentials = crm_strdup_printf("ACLs as evaluated for user %s",
244 cred);
245
246 comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
247 free(credentials);
248 if (comment == NULL) {
249 xmlFreeNode(target);
250 return EINVAL;
251 }
252 xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
253 *acl_evaled_doc = target->doc;
254 return pcmk_rc_ok;
255 } else {
256 xmlFreeNode(target);
257 return ret; //for now, it should be some kind of error
258 }
259}
260
261int
262pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
263 xmlChar **doc_txt_ptr)
264{
265 xmlDoc *xslt_doc;
266 xsltStylesheet *xslt;
267 xsltTransformContext *xslt_ctxt;
268 xmlDoc *res;
269 char *sfile;
270 static const char *params_namespace[] = {
271 "accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
272 "accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
273 "accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
274 "accessrendercfg:c-reset", "",
275 "accessrender:extra-spacing", "no",
276 "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
277 NULL
278 }, *params_useansi[] = {
279 /* start with hard-coded defaults, then adapt per the template ones */
280 "accessrendercfg:c-writable", "\x1b[32m",
281 "accessrendercfg:c-readable", "\x1b[34m",
282 "accessrendercfg:c-denied", "\x1b[31m",
283 "accessrendercfg:c-reset", "\x1b[0m",
284 "accessrender:extra-spacing", "no",
285 "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
286 NULL
287 }, *params_noansi[] = {
288 "accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
289 "accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
290 "accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
291 "accessrendercfg:c-reset", "",
292 "accessrender:extra-spacing", "yes",
293 "accessrender:self-reproducing-prefix", "",
294 NULL
295 };
296 const char **params;
297 int rc = pcmk_rc_ok;
298 xmlParserCtxtPtr parser_ctxt;
299
300 /* unfortunately, the input (coming from CIB originally) was parsed with
301 blanks ignored, and since the output is a conversion of XML to text
302 format (we would be covered otherwise thanks to implicit
303 pretty-printing), we need to dump the tree to string output first,
304 only to subsequently reparse it -- this time with blanks honoured */
305 xmlChar *annotated_dump;
306 int dump_size;
307
309
310 // Color is the default render mode for terminals; text is default otherwise
311 if (how == pcmk__acl_render_default) {
312 if (isatty(STDOUT_FILENO)) {
314 } else {
316 }
317 }
318
319 xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
320 res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
321 XML_PARSE_NONET);
322 CRM_ASSERT(res != NULL);
323 xmlFree(annotated_dump);
324 xmlFreeDoc(annotated_doc);
325 annotated_doc = res;
326
328 "access-render-2");
329 parser_ctxt = xmlNewParserCtxt();
330
331 CRM_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) {
349 params = params_namespace;
350 break;
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 xmlFreeDoc(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 xmlFreeDoc(res);
387 if (temp != 0) {
388 rc = EINVAL;
389 }
390 }
391
392done:
393 if (xslt != NULL) {
394 xsltFreeStylesheet(xslt);
395 }
396 free(sfile);
397 return rc;
398}
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition acl.c:754
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition acl.c:658
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition acl.c:354
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
A dumping ground.
#define crm_crit(fmt, args...)
Definition logging.h:386
#define CRM_CHECK(expr, failure_action)
Definition logging.h:245
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition nvpair.c:446
#define ACL_NS_Q_WRITABLE
Definition pcmk_acl.c:35
#define ACL_NS_PREFIX
Definition pcmk_acl.c:33
#define ACL_NS_Q_DENIED
Definition pcmk_acl.c:37
int pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc, xmlDoc **acl_evaled_doc)
Annotate CIB with XML namespaces indicating ACL evaluation results.
Definition pcmk_acl.c:203
#define ACL_NS_Q_PREFIX
Definition pcmk_acl.c:34
#define ACL_NS_Q_READABLE
Definition pcmk_acl.c:36
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition pcmk_acl.c:262
const char * target
Definition pcmk_fence.c:29
pcmk__acl_render_how
Definition pcmki_acl.h:15
@ pcmk__acl_render_text
Definition pcmki_acl.h:18
@ pcmk__acl_render_color
Definition pcmki_acl.h:19
@ pcmk__acl_render_namespace
Definition pcmki_acl.h:17
@ pcmk__acl_render_none
Definition pcmki_acl.h:16
@ pcmk__acl_render_default
Definition pcmki_acl.h:20
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition pcmki_acl.h:24
#define CRM_ASSERT(expr)
Definition results.h:42
@ pcmk_rc_ok
Definition results.h:162
@ pcmk_rc_schema_validation
Definition results.h:141
@ pcmk_rc_already
Definition results.h:153
#define pcmk__mem_assert(ptr)
int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
Definition schemas.c:691
Wrappers for and extensions to libxml2.
const xmlChar * pcmkXmlStr
Definition xml.h:41
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition xml.c:883
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition xml.c:89
@ pcmk__xf_acl_write
@ pcmk__xf_tracking
@ pcmk__xf_acl_read
@ pcmk__xml_artefact_ns_base_xslt
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition xml.c:2241
#define PCMK_XA_VALIDATE_WITH
Definition xml_names.h:436