pacemaker 2.1.5-a3f44794f94
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
40static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
41static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
42static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied";
43
55static void
56pcmk__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
106static int
107pcmk__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
187int
188pcmk__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
242int
243pcmk__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}
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:751
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:655
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:353
uint32_t version
Definition: remote.c:1
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
A dumping ground.
#define crm_crit(fmt, args...)
Definition: logging.h:358
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:227
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:120
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:517
#define ACL_NS_Q_WRITABLE
Definition: pcmk_acl.c:36
#define ACL_NS_PREFIX
Definition: pcmk_acl.c:34
#define ACL_NS_Q_DENIED
Definition: pcmk_acl.c:38
#define ACL_NS_Q_PREFIX
Definition: pcmk_acl.c:35
#define ACL_NS_Q_READABLE
Definition: pcmk_acl.c:37
int pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc, xmlDoc **acl_evaled_doc)
Mark CIB with namespace-encoded result of ACLs eval'd per credential.
Definition: pcmk_acl.c:188
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition: pcmk_acl.c:243
const char * target
Definition: pcmk_fence.c:29
pcmk__acl_render_how
Definition: pcmki_acl.h:20
@ pcmk__acl_render_text
Definition: pcmki_acl.h:22
@ pcmk__acl_render_color
Definition: pcmki_acl.h:23
@ pcmk__acl_render_namespace
Definition: pcmki_acl.h:21
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition: pcmki_acl.h:33
#define CRM_ASSERT(expr)
Definition: results.h:42
@ pcmk_rc_ok
Definition: results.h:148
@ pcmk_rc_schema_validation
Definition: results.h:128
@ pcmk_rc_already
Definition: results.h:140
Wrappers for and extensions to libxml2.
int get_schema_version(const char *name)
Definition: schemas.c:1033
const xmlChar * pcmkXmlStr
Definition: xml.h:50
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:891
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:108
@ pcmk__xf_acl_write
Definition: xml_internal.h:334
@ pcmk__xf_tracking
Definition: xml_internal.h:327
@ pcmk__xf_acl_read
Definition: xml_internal.h:333
@ pcmk__xml_artefact_ns_base_xslt
Definition: xml_internal.h:153
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:3063