pacemaker  3.0.0-d8340737c4
Scalable High-Availability cluster resource manager
digest.c
Go to the documentation of this file.
1 /*
2  * Copyright 2015-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 <stdbool.h>
13 #include <stdio.h>
14 #include <unistd.h>
15 #include <string.h>
16 #include <stdlib.h>
17 
18 #include <glib.h> // GString, etc.
19 #include <gnutls/crypto.h> // gnutls_hash_fast(), gnutls_hash_get_len()
20 #include <gnutls/gnutls.h> // gnutls_strerror()
21 
22 #include <crm/crm.h>
23 #include <crm/common/xml.h>
24 #include "crmcommon_private.h"
25 
26 #define BEST_EFFORT_STATUS 0
27 
28 /*
29  * Pacemaker uses digests (MD5 hashes) of stringified XML to detect changes in
30  * the CIB as a whole, a particular resource's agent parameters, and the device
31  * parameters last used to unfence a particular node.
32  *
33  * "v2" digests hash pcmk__xml_string() directly, while less efficient "v1"
34  * digests do the same with a prefixed space, suffixed newline, and optional
35  * pre-sorting.
36  *
37  * On-disk CIB digests use v1 without sorting.
38  *
39  * Operation digests use v1 with sorting, and are stored in a resource's
40  * operation history in the CIB status section. They come in three flavors:
41  * - a digest of (nearly) all resource parameters and options, used to detect
42  * any resource configuration change;
43  * - a digest of resource parameters marked as nonreloadable, used to decide
44  * whether a reload or full restart is needed after a configuration change;
45  * - and a digest of resource parameters not marked as private, used in
46  * simulations where private parameters have been removed from the input.
47  *
48  * Unfencing digests are set as node attributes, and are used to require
49  * that nodes be unfenced again after a device's configuration changes.
50  */
51 
60 static GString *
61 dump_xml_for_digest(xmlNodePtr xml)
62 {
63  GString *buffer = g_string_sized_new(1024);
64 
65  /* for compatibility with the old result which is used for v1 digests */
66  g_string_append_c(buffer, ' ');
67  pcmk__xml_string(xml, 0, buffer, 0);
68  g_string_append_c(buffer, '\n');
69 
70  return buffer;
71 }
72 
83 static char *
84 calculate_xml_digest_v1(xmlNode *input)
85 {
86  GString *buffer = dump_xml_for_digest(input);
87  char *digest = NULL;
88 
89  // buffer->len > 2 for initial space and trailing newline
90  CRM_CHECK(buffer->len > 2,
91  g_string_free(buffer, TRUE);
92  return NULL);
93 
94  digest = crm_md5sum((const char *) buffer->str);
95  crm_log_xml_trace(input, "digest:source");
96 
97  g_string_free(buffer, TRUE);
98  return digest;
99 }
100 
109 char *
111 {
112  /* Always use the v1 format for on-disk digests.
113  * * Switching to v2 affects even full-restart upgrades, so it would be a
114  * compatibility nightmare.
115  * * We only use this once at startup. All other invocations are in a
116  * separate child process.
117  */
118  return calculate_xml_digest_v1(input);
119 }
120 
132 char *
134 {
135  /* Switching to v2 digests would likely cause restarts during rolling
136  * upgrades.
137  *
138  * @TODO Confirm this. Switch to v2 if safe, or drop this TODO otherwise.
139  */
140  xmlNode *sorted = pcmk__xml_copy(NULL, input);
141  char *digest = NULL;
142 
143  pcmk__xe_sort_attrs(sorted);
144  digest = calculate_xml_digest_v1(sorted);
145 
146  pcmk__xml_free(sorted);
147  return digest;
148 }
149 
159 char *
160 pcmk__digest_xml(xmlNode *xml, bool filter)
161 {
162  /* @TODO Filtering accounts for significant CPU usage. Consider removing if
163  * possible.
164  */
165  char *digest = NULL;
166  GString *buf = g_string_sized_new(1024);
167 
168  pcmk__xml_string(xml, (filter? pcmk__xml_fmt_filtered : 0), buf, 0);
169  digest = crm_md5sum(buf->str);
170 
172  {
173  char *trace_file = crm_strdup_printf("%s/digest-%s",
174  pcmk__get_tmpdir(), digest);
175 
176  crm_trace("Saving %s.%s.%s to %s",
180  trace_file);
181  save_xml_to_file(xml, "digest input", trace_file);
182  free(trace_file);
183  },
184  {}
185  );
186  g_string_free(buf, TRUE);
187  return digest;
188 }
189 
199 bool
200 pcmk__verify_digest(xmlNode *input, const char *expected)
201 {
202  char *calculated = NULL;
203  bool passed;
204 
205  if (input != NULL) {
206  calculated = pcmk__digest_on_disk_cib(input);
207  if (calculated == NULL) {
208  crm_perror(LOG_ERR, "Could not calculate digest for comparison");
209  return false;
210  }
211  }
212  passed = pcmk__str_eq(expected, calculated, pcmk__str_casei);
213  if (passed) {
214  crm_trace("Digest comparison passed: %s", calculated);
215  } else {
216  crm_err("Digest comparison failed: expected %s, calculated %s",
217  expected, calculated);
218  }
219  free(calculated);
220  return passed;
221 }
222 
231 bool
233 {
234  static const char *filter[] = {
240  };
241 
242  for (int i = 0; i < PCMK__NELEM(filter); i++) {
243  if (strcmp(name, filter[i]) == 0) {
244  return true;
245  }
246  }
247  return false;
248 }
249 
250 char *
251 crm_md5sum(const char *buffer)
252 {
253  char *digest = NULL;
254  gchar *raw_digest = NULL;
255 
256  if (buffer == NULL) {
257  return NULL;
258  }
259 
260  raw_digest = g_compute_checksum_for_string(G_CHECKSUM_MD5, buffer, -1);
261 
262  if (raw_digest == NULL) {
263  crm_err("Failed to calculate hash");
264  return NULL;
265  }
266 
267  digest = pcmk__str_copy(raw_digest);
268  g_free(raw_digest);
269 
270  crm_trace("Digest %s.", digest);
271  return digest;
272 }
273 
274 // Return true if a is an attribute that should be filtered
275 static bool
276 should_filter_for_digest(xmlAttrPtr a, void *user_data)
277 {
278  if (strncmp((const char *) a->name, CRM_META "_",
279  sizeof(CRM_META " ") - 1) == 0) {
280  return true;
281  }
282  return pcmk__str_any_of((const char *) a->name,
283  PCMK_XA_ID,
288  "pcmk_external_ip",
289  NULL);
290 }
291 
298 void
299 pcmk__filter_op_for_digest(xmlNode *param_set)
300 {
301  char *key = NULL;
302  char *timeout = NULL;
303  guint interval_ms = 0;
304 
305  if (param_set == NULL) {
306  return;
307  }
308 
309  /* Timeout is useful for recurring operation digests, so grab it before
310  * removing meta-attributes
311  */
313  if (crm_element_value_ms(param_set, key, &interval_ms) != pcmk_ok) {
314  interval_ms = 0;
315  }
316  free(key);
317  key = NULL;
318  if (interval_ms != 0) {
320  timeout = crm_element_value_copy(param_set, key);
321  }
322 
323  // Remove all CRM_meta_* attributes and certain other attributes
324  pcmk__xe_remove_matching_attrs(param_set, should_filter_for_digest, NULL);
325 
326  // Add timeout back for recurring operation digests
327  if (timeout != NULL) {
328  crm_xml_add(param_set, key, timeout);
329  }
330  free(timeout);
331  free(key);
332 }
333 
334 // Deprecated functions kept only for backward API compatibility
335 // LCOV_EXCL_START
336 
337 #include <crm/common/xml_compat.h>
339 
340 char *
342 {
343  return calculate_xml_digest_v1(input);
344 }
345 
346 char *
348 {
349  xmlNode *sorted = sorted_xml(input, NULL, true);
350  char *digest = calculate_xml_digest_v1(sorted);
351 
352  pcmk__xml_free(sorted);
353  return digest;
354 }
355 
356 char *
357 calculate_xml_versioned_digest(xmlNode *input, gboolean sort,
358  gboolean do_filter, const char *version)
359 {
360  if ((version == NULL) || (compare_version("3.0.5", version) > 0)) {
361  xmlNode *sorted = NULL;
362  char *digest = NULL;
363 
364  if (sort) {
365  xmlNode *sorted = sorted_xml(input, NULL, true);
366 
367  input = sorted;
368  }
369 
370  crm_trace("Using v1 digest algorithm for %s",
371  pcmk__s(version, "unknown feature set"));
372 
373  digest = calculate_xml_digest_v1(input);
374 
375  pcmk__xml_free(sorted);
376  return digest;
377  }
378  crm_trace("Using v2 digest algorithm for %s", version);
379  return pcmk__digest_xml(input, do_filter);
380 }
381 
382 // LCOV_EXCL_STOP
383 // End deprecated API
Deprecated Pacemaker XML element API.
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
A dumping ground.
#define pcmk__if_tracing(if_action, else_action)
#define PCMK_XA_UPDATE_ORIGIN
Definition: xml_names.h:437
const char * name
Definition: cib.c:26
void save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename)
Definition: xml_io.c:645
void pcmk__xml_string(const xmlNode *data, uint32_t options, GString *buffer, int depth)
Definition: xml_io.c:411
char * calculate_on_disk_digest(xmlNode *input)
Definition: digest.c:341
char * pcmk__digest_xml(xmlNode *xml, bool filter)
Definition: digest.c:160
Exclude certain XML attributes (for calculating digests)
Definition: xml_internal.h:146
#define PCMK_XA_NUM_UPDATES
Definition: xml_names.h:341
char * pcmk__digest_operation(xmlNode *input)
Definition: digest.c:133
void pcmk__xe_sort_attrs(xmlNode *xml)
Definition: xml_element.c:312
#define PCMK_XA_CIB_LAST_WRITTEN
Definition: xml_names.h:244
#define PCMK__META_ON_NODE
Deprecated Pacemaker XML API.
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
const char * crm_xml_add(xmlNode *node, const char *name, const char *value)
Create an XML attribute with specified name and value.
Definition: xml_element.c:1015
void pcmk__filter_op_for_digest(xmlNode *param_set)
Definition: digest.c:299
int crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
Retrieve the millisecond value of an XML attribute.
Definition: xml_element.c:1322
char * crm_element_value_copy(const xmlNode *data, const char *name)
Retrieve a copy of the value of an XML attribute.
Definition: xml_element.c:1466
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
#define PCMK__XA_OP_DIGEST
#define crm_trace(fmt, args...)
Definition: logging.h:372
#define PCMK_XA_UPDATE_USER
Definition: xml_names.h:438
#define PCMK__META_ON_NODE_UUID
#define PCMK_XA_EPOCH
Definition: xml_names.h:268
Wrappers for and extensions to libxml2.
#define PCMK__NELEM(a)
Definition: internal.h:49
void xmlNode * sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive)
Definition: xml_element.c:1581
#define PCMK_XA_ID
Definition: xml_names.h:301
bool pcmk__xa_filterable(const char *name)
Definition: digest.c:232
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1051
#define pcmk__str_copy(str)
char * calculate_xml_versioned_digest(xmlNode *input, gboolean sort, gboolean do_filter, const char *version)
Definition: digest.c:357
#define PCMK_META_TIMEOUT
Definition: options.h:114
char * pcmk__digest_on_disk_cib(xmlNode *input)
Definition: digest.c:110
#define PCMK_XA_CRM_FEATURE_SET
Definition: xml_names.h:254
const char * pcmk__get_tmpdir(void)
Definition: io.c:543
bool pcmk__verify_digest(xmlNode *input, const char *expected)
Definition: digest.c:200
#define PCMK_META_INTERVAL
Definition: options.h:91
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:299
#define CRM_META
Definition: crm.h:75
#define crm_err(fmt, args...)
Definition: logging.h:359
xmlNode * input
int compare_version(const char *version1, const char *version2)
Definition: utils.c:202
char * calculate_operation_digest(xmlNode *input, const char *version)
Definition: digest.c:347
#define pcmk_ok
Definition: results.h:65
#define crm_log_xml_trace(xml, text)
Definition: logging.h:380
char * crm_md5sum(const char *buffer)
Definition: digest.c:251
#define PCMK_XA_ADMIN_EPOCH
Definition: xml_names.h:232
#define PCMK_XA_CRM_DEBUG_ORIGIN
Definition: xml_names.h:253
unsigned int timeout
Definition: pcmk_fence.c:34
#define PCMK_XA_UPDATE_CLIENT
Definition: xml_names.h:436
uint32_t version
Definition: remote.c:209
void pcmk__xe_remove_matching_attrs(xmlNode *element, bool(*match)(xmlAttrPtr, void *), void *user_data)
Definition: xml_element.c:379
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
char * crm_meta_name(const char *field)
Get the environment variable equivalent of a meta-attribute name.
Definition: nvpair.c:407