cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

server_access.c
Go to the documentation of this file.
1 /*
2  Copyright 2019 Northern.tech AS
3 
4  This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6  This program is free software; you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by the
8  Free Software Foundation; version 3.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18 
19  To the extent this program is licensed as part of the Enterprise
20  versions of CFEngine, the applicable Commercial Open Source License
21  (COSL) may apply to this file if you as a licensee so wish it. See
22  included file COSL.txt.
23 */
24 
25 
26 #include <platform.h>
27 
28 #include "server_access.h"
29 #include "strlist.h"
30 #include "server.h"
31 
32 #include <addr_lib.h> /* FuzzySetMatch */
33 #include <string_lib.h> /* StringMatchFull TODO REMOVE */
34 #include <misc_lib.h>
35 #include <file_lib.h>
36 #include <regex.h>
37 
38 
39 struct acl *paths_acl;
40 struct acl *classes_acl;
41 struct acl *vars_acl;
42 struct acl *literals_acl;
43 struct acl *query_acl;
44 struct acl *bundles_acl;
45 struct acl *roles_acl;
46 
47 
48 /**
49  * Run this function on every resource (file, class, var etc) access to
50  * grant/deny rights. Currently it checks if:
51  * 1. #ipaddr matches the subnet expression in {admit,deny}_ips
52  * 2. #hostname matches the subdomain expression in {admit,deny}_hostnames
53  * 3. #key is searched as-is in {admit,deny}_keys
54  * 4. #username is searched as-is in {admit,deny}_usernames
55  *
56  * @param #found If not NULL then it returns whether the denial was implicit
57  * (found=false) or explicit (found=true). If not NULL it also
58  * changes the way the ACLs are traversed to a SLOWER mode,
59  * since every entry has to be checked for explicit denial.
60  *
61  * @return Default is false, i.e. deny. If a match is found in #acl->admit.*
62  * then return true, unless a match is also found in #acl->deny.* in
63  * which case return false.
64  *
65  * @TODO preprocess our global ACL the moment a client connects, and store in
66  * ServerConnectionState a list of objects that he can access. That way
67  * only his relevant resources will be stored in e.g. {admit,deny}_paths
68  * lists, and running through these two lists on every file request will
69  * be much faster.
70  */
71 static bool access_CheckResource(const struct resource_acl *acl,
72  bool *found,
73  const char *ipaddr, const char *hostname,
74  const char *key, const char *username)
75 {
76  bool access = false; /* DENY by default */
77  bool have_match = false; /* No matching rule found yet */
78 
79  /* First we check for admission, secondly for denial, so that denial takes
80  * precedence. */
81 
82  if (!NULL_OR_EMPTY(ipaddr) && acl->admit.ips != NULL)
83  {
84  /* Still using legacy code here, doing linear search over all IPs in
85  * textual representation... too CPU intensive! TODO store the ACL as
86  * one list of struct sockaddr_storage, together with CIDR notation
87  * subnet length.
88  */
89 
90  const char *rule = NULL;
91  for (int i = 0; i < StrList_Len(acl->admit.ips); i++)
92  {
93  if (FuzzySetMatch(StrList_At(acl->admit.ips, i), ipaddr) == 0 ||
94  /* Legacy regex matching, TODO DEPRECATE */
95  StringMatchFull(StrList_At(acl->admit.ips, i), ipaddr))
96  {
97  rule = StrList_At(acl->admit.ips, i);
98  break;
99  }
100  }
101 
102  if (rule != NULL)
103  {
105  "Admit IP due to rule: %s",
106  rule);
107  access = true;
108  have_match = true;
109  }
110  }
111  if (!access && !NULL_OR_EMPTY(hostname) &&
112  acl->admit.hostnames != NULL)
113  {
114  size_t pos = StrList_SearchLongestPrefix(acl->admit.hostnames,
115  hostname, 0,
116  '.', false);
117 
118  /* === Legacy regex matching, slow, TODO DEPRECATE === */
119  if (pos == (size_t) -1)
120  {
121  for (int i = 0; i < StrList_Len(acl->admit.hostnames); i++)
122  {
123  if (StringMatchFull(StrList_At(acl->admit.hostnames, i),
124  hostname))
125  {
126  pos = i;
127  break;
128  }
129  }
130  }
131  /* =================================================== */
132 
133  if (pos != (size_t) -1)
134  {
136  "Admit hostname due to rule: %s",
137  StrList_At(acl->admit.hostnames, pos));
138  access = true;
139  have_match = true;
140  }
141  }
142  if (!access && !NULL_OR_EMPTY(key) &&
143  acl->admit.keys != NULL)
144  {
145  size_t pos;
146  bool ret = StrList_BinarySearch(acl->admit.keys, key, &pos);
147  if (ret)
148  {
150  "Admit key due to rule: %s",
151  StrList_At(acl->admit.keys, pos));
152  access = true;
153  have_match = true;
154  }
155  }
156  if (!access && !NULL_OR_EMPTY(username) &&
157  acl->admit.usernames != NULL)
158  {
159  size_t pos;
160  bool ret = StrList_BinarySearch(acl->admit.usernames, username, &pos);
161  if (ret)
162  {
164  "Admit username due to rule: %s",
165  StrList_At(acl->admit.usernames, pos));
166  access = true;
167  have_match = true;
168  }
169  }
170 
171 
172  /* An admit rule was not found, and we don't care whether the denial is
173  * explicit or implicit: we can finish now. */
174  if (!access && found == NULL)
175  {
176  assert(!have_match);
177  return false; /* EARLY RETURN! */
178  }
179 
180  /* If access has been granted, we might need to deny it based on ACL. */
181  /* Same goes if access has not been granted and "found" is not NULL, in
182  * which case we have to return in "found", whether an explicit denial
183  * rule matched or not. */
184 
185  assert((access && have_match) ||
186  (!access && !have_match && found != NULL));
187 
188  if ((access || !have_match) &&
189  !NULL_OR_EMPTY(ipaddr) &&
190  acl->deny.ips != NULL)
191  {
192  const char *rule = NULL;
193  for (int i = 0; i < StrList_Len(acl->deny.ips); i++)
194  {
195  if (FuzzySetMatch(StrList_At(acl->deny.ips, i), ipaddr) == 0 ||
196  /* Legacy regex matching, TODO DEPRECATE */
197  StringMatchFull(StrList_At(acl->deny.ips, i), ipaddr))
198  {
199  rule = StrList_At(acl->deny.ips, i);
200  break;
201  }
202  }
203 
204  if (rule != NULL)
205  {
207  "Deny IP due to rule: %s",
208  rule);
209  access = false;
210  have_match = true;
211  }
212  }
213  if ((access || !have_match) &&
214  !NULL_OR_EMPTY(hostname) &&
215  acl->deny.hostnames != NULL)
216  {
217  size_t pos = StrList_SearchLongestPrefix(acl->deny.hostnames,
218  hostname, 0,
219  '.', false);
220 
221  /* === Legacy regex matching, slow, TODO DEPRECATE === */
222  if (pos == (size_t) -1)
223  {
224  for (int i = 0; i < StrList_Len(acl->deny.hostnames); i++)
225  {
226  if (StringMatchFull(StrList_At(acl->deny.hostnames, i),
227  hostname))
228  {
229  pos = i;
230  break;
231  }
232  }
233  }
234  /* =================================================== */
235 
236  if (pos != (size_t) -1)
237  {
239  "Deny hostname due to rule: %s",
240  StrList_At(acl->deny.hostnames, pos));
241  access = false;
242  have_match = true;
243  }
244  }
245  if ((access || !have_match) &&
246  !NULL_OR_EMPTY(key) &&
247  acl->deny.keys != NULL)
248  {
249  size_t pos;
250  bool ret = StrList_BinarySearch(acl->deny.keys, key, &pos);
251  if (ret)
252  {
254  "Deny key due to rule: %s",
255  StrList_At(acl->deny.keys, pos));
256  access = false;
257  have_match = true;
258  }
259  }
260  if ((access || !have_match) &&
261  !NULL_OR_EMPTY(username) &&
262  acl->deny.usernames != NULL)
263  {
264  size_t pos;
265  bool ret = StrList_BinarySearch(acl->deny.usernames, username, &pos);
266  if (ret)
267  {
269  "Deny username due to rule: %s",
270  StrList_At(acl->deny.usernames, pos));
271  access = false;
272  have_match = true;
273  }
274  }
275 
276  /* We can't have implicit admittance,
277  admittance must always be explicit. */
278  assert(! (access && !have_match));
279 
280  if (found != NULL)
281  {
282  *found = have_match;
283  }
284  return access;
285 }
286 
287 
288 /**
289  * Search #req_path in #acl, if found check its rules. The longest parent
290  * directory of #req_path is searched, or an exact match. Directories *must*
291  * end with FILE_SEPARATOR in the ACL list.
292  *
293  * @return If ACL entry is found, and host is listed in there return
294  * true. Else return false.
295  */
296 bool acl_CheckPath(const struct acl *acl, const char *reqpath,
297  const char *ipaddr, const char *hostname,
298  const char *key)
299 {
300  bool access = false; /* Deny access by default */
301  size_t reqpath_len = strlen(reqpath);
302 
303  /* CHECK 1: Search for parent directory or exact entry in ACL. */
305  reqpath, reqpath_len,
306  FILE_SEPARATOR, true);
307 
308  if (pos != (size_t) -1) /* acl entry was found */
309  {
310  const struct resource_acl *racl = &acl->acls[pos];
311  bool ret = access_CheckResource(racl, NULL, ipaddr, hostname, key, NULL);
312  if (ret == true) /* entry found that grants access */
313  {
314  access = true;
315  }
317  "acl_CheckPath: '%s' found in ACL entry '%s', admit=%s",
318  reqpath, acl->resource_names->list[pos]->str,
319  ret == true ? "true" : "false");
320  }
321 
322  /* CHECK 2: replace ACL entry parts with special variables (if applicable),
323  * e.g. turn "/path/to/192.168.1.1.json"
324  * to "/path/to/$(connection.ip).json" */
325  char mangled_path[PATH_MAX];
326  memcpy(mangled_path, reqpath, reqpath_len + 1);
327  size_t mangled_path_len =
328  ReplaceSpecialVariables(mangled_path, sizeof(mangled_path),
329  ipaddr, "$(connection.ip)",
330  hostname, "$(connection.hostname)",
331  key, "$(connection.key)");
332 
333  /* If there were special variables replaced */
334  if (mangled_path_len != 0 &&
335  mangled_path_len != (size_t) -1) /* Overflow, TODO handle separately. */
336  {
338  mangled_path, mangled_path_len,
339  FILE_SEPARATOR, true);
340 
341  if (pos2 != (size_t) -1) /* acl entry was found */
342  {
343  /* TODO make sure this match is more specific than the other one. */
344  const struct resource_acl *racl = &acl->acls[pos2];
345  /* Check if the magic strings are allowed or denied. */
346  bool ret =
348  "$(connection.ip)",
349  "$(connection.hostname)",
350  "$(connection.key)", NULL);
351  if (ret == true) /* entry found that grants access */
352  {
353  access = true;
354  }
356  "acl_CheckPath: '%s' found in ACL entry '%s', admit=%s",
357  mangled_path, acl->resource_names->list[pos2]->str,
358  ret == true ? "true" : "false");
359  }
360  }
361 
362  return access;
363 }
364 
365 bool acl_CheckExact(const struct acl *acl, const char *req_string,
366  const char *ipaddr, const char *hostname,
367  const char *key)
368 {
369  bool access = false;
370 
371  size_t pos = -1;
372  bool found = StrList_BinarySearch(acl->resource_names, req_string, &pos);
373  if (found)
374  {
375  const struct resource_acl *racl = &acl->acls[pos];
376  bool ret = access_CheckResource(racl, NULL,
377  ipaddr, hostname, key, NULL);
378  if (ret == true) /* entry found that grants access */
379  {
380  access = true;
381  }
382  }
383 
384  return access;
385 }
386 
387 /**
388  * Go linearly over all the #acl and check every rule if it matches.
389  * ADMIT only if at least one rule matches admit and none matches deny.
390  * DENY if no rule matches OR if at least one matches deny.
391  */
392 bool acl_CheckRegex(const struct acl *acl, const char *req_string,
393  const char *ipaddr, const char *hostname,
394  const char *key, const char *username)
395 {
396  bool retval = false;
397 
398  /* For all ACLs */
399  for (size_t i = 0; i < acl->len; i++)
400  {
401  const char *regex = acl->resource_names->list[i]->str;
402 
403  /* Does this ACL matches the req_string? */
404  if (StringMatchFull(regex, req_string))
405  {
406  const struct resource_acl *racl = &acl->acls[i];
407 
408  /* Does this ACL apply to this host? */
409  bool found;
410  bool admit = access_CheckResource(racl, &found,
411  ipaddr, hostname, key, username);
412  if (found && !admit)
413  {
414  return false;
415  }
416  else if (found && admit)
417  {
418  retval = true;
419  }
420  else
421  {
422  /* If it's not found, there should be no admittance. */
423  assert(!found);
424  assert(!admit);
425  /* We are not touching retval, because it was possibly found
426  * before and retval has been set to "true". */
427  }
428  }
429  }
430 
431  return retval;
432 }
433 
434 
435 /**
436  * Search the list of resources for the handle. If found return the index of
437  * the resource ACL that corresponds to that handle, else add the handle with
438  * empty ACL, reallocating if necessary. The new handle is inserted in the
439  * proper position to keep the acl->resource_names list sorted.
440  *
441  * @note acl->resource_names list should already be sorted, no problem if all
442  * inserts are done with this function.
443  *
444  * @return the index of the resource_acl corresponding to handle. -1 means
445  * reallocation failed, but existing values are still valid.
446  */
447 size_t acl_SortedInsert(struct acl **a, const char *handle)
448 {
449  assert(handle != NULL);
450 
451  struct acl *acl = *a; /* for clarity */
452 
453  size_t position = (size_t) -1;
455  handle, &position);
456  if (found)
457  {
458  /* Found it, return existing entry. */
459  assert(position < acl->len);
460  return position;
461  }
462 
463  /* handle is not in acl, we must insert at the position returned. */
464  assert(position <= acl->len);
465 
466  /* 1. Check if reallocation is needed. */
467  if (acl->len == acl->alloc_len)
468  {
469  size_t new_alloc_len = acl->alloc_len * 2;
470  if (new_alloc_len == 0)
471  {
472  new_alloc_len = 1;
473  }
474 
475  struct acl *p =
476  realloc(acl, sizeof(*p) + sizeof(*p->acls) * new_alloc_len);
477  if (p == NULL)
478  {
479  return (size_t) -1;
480  }
481 
482  acl = p;
483  acl->alloc_len = new_alloc_len;
484  *a = acl; /* Change the caller's variable */
485  }
486 
487  /* 2. We now have enough space, so insert the resource at the proper
488  index. */
489  size_t ret = StrList_Insert(&acl->resource_names,
490  handle, position);
491  if (ret == (size_t) -1)
492  {
493  /* realloc() failed but the data structure is still valid. */
494  return (size_t) -1;
495  }
496 
497  /* 3. Make room. */
498  memmove(&acl->acls[position + 1], &acl->acls[position],
499  (acl->len - position) * sizeof(acl->acls[position]));
500  acl->len++;
501 
502  /* 4. Initialise all ACLs for the resource as empty. */
503  acl->acls[position] = (struct resource_acl) { {0}, {0} }; /* NULL acls <=> empty */
504 
505  Log(LOG_LEVEL_DEBUG, "Inserted in ACL position %zu: %s",
506  position, handle);
507 
508  assert(acl->len == StrList_Len(acl->resource_names));
509 
510  return position;
511 }
512 
513 void acl_Free(struct acl *a)
514 {
516 
517  size_t i;
518  for (i = 0; i < a->len; i++)
519  {
520  StrList_Free(&a->acls[i].admit.ips);
523  StrList_Free(&a->acls[i].admit.keys);
524  StrList_Free(&a->acls[i].deny.ips);
526  StrList_Free(&a->acls[i].deny.keys);
527  }
528 
529  free(a);
530 }
531 
532 void acl_Summarise(const struct acl *acl, const char *title)
533 {
534  assert(acl->len == StrList_Len(acl->resource_names));
535 
536  size_t i, j;
537  for (i = 0; i < acl->len; i++)
538  {
539  Log(LOG_LEVEL_VERBOSE, "\t%s: %s",
540  title, StrList_At(acl->resource_names, i));
541 
542  const struct resource_acl *racl = &acl->acls[i];
543 
544  for (j = 0; j < StrList_Len(racl->admit.ips); j++)
545  {
546  Log(LOG_LEVEL_VERBOSE, "\t\tadmit_ips: %s",
547  StrList_At(racl->admit.ips, j));
548  }
549  for (j = 0; j < StrList_Len(racl->admit.hostnames); j++)
550  {
551  Log(LOG_LEVEL_VERBOSE, "\t\tadmit_hostnames: %s",
552  StrList_At(racl->admit.hostnames, j));
553  }
554  for (j = 0; j < StrList_Len(racl->admit.keys); j++)
555  {
556  Log(LOG_LEVEL_VERBOSE, "\t\tadmit_keys: %s",
557  StrList_At(racl->admit.keys, j));
558  }
559  for (j = 0; j < StrList_Len(racl->deny.ips); j++)
560  {
561  Log(LOG_LEVEL_VERBOSE, "\t\tdeny_ips: %s",
562  StrList_At(racl->deny.ips, j));
563  }
564  for (j = 0; j < StrList_Len(racl->deny.hostnames); j++)
565  {
566  Log(LOG_LEVEL_VERBOSE, "\t\tdeny_hostnames: %s",
567  StrList_At(racl->deny.hostnames, j));
568  }
569  for (j = 0; j < StrList_Len(racl->deny.keys); j++)
570  {
571  Log(LOG_LEVEL_VERBOSE, "\t\tdeny_keys: %s",
572  StrList_At(racl->deny.keys, j));
573  }
574  }
575 }
int FuzzySetMatch(const char *s1, const char *s2)
Definition: addr_lib.c:41
#define realloc
Definition: cf3lex.c:805
void free(void *)
#define FILE_SEPARATOR
Definition: file_lib.h:102
#define NULL
Definition: getopt1.c:56
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
#define PATH_MAX
Definition: platform.h:176
bool StringMatchFull(const char *regex, const char *str)
Definition: regex.c:106
bool acl_CheckRegex(const struct acl *acl, const char *req_string, const char *ipaddr, const char *hostname, const char *key, const char *username)
bool acl_CheckPath(const struct acl *acl, const char *reqpath, const char *ipaddr, const char *hostname, const char *key)
bool acl_CheckExact(const struct acl *acl, const char *req_string, const char *ipaddr, const char *hostname, const char *key)
struct acl * classes_acl
Definition: server_access.c:40
void acl_Summarise(const struct acl *acl, const char *title)
static bool access_CheckResource(const struct resource_acl *acl, bool *found, const char *ipaddr, const char *hostname, const char *key, const char *username)
Definition: server_access.c:71
size_t acl_SortedInsert(struct acl **a, const char *handle)
struct acl * paths_acl
Definition: server_access.c:39
struct acl * roles_acl
Definition: server_access.c:45
struct acl * query_acl
Definition: server_access.c:43
struct acl * literals_acl
Definition: server_access.c:42
struct acl * bundles_acl
Definition: server_access.c:44
struct acl * vars_acl
Definition: server_access.c:41
void acl_Free(struct acl *a)
size_t ReplaceSpecialVariables(char *buf, size_t buf_size, const char *find1, const char *repl1, const char *find2, const char *repl2, const char *find3, const char *repl3)
#define NULL_OR_EMPTY(str)
Definition: string_lib.h:43
bool StrList_BinarySearch(const StrList *slp, const char *s, size_t *position)
Definition: strlist.c:468
char * StrList_At(const StrList *sl, size_t idx)
Definition: strlist.c:210
size_t StrList_Len(const StrList *sl)
Definition: strlist.c:205
void StrList_Free(StrList **sl)
Definition: strlist.c:363
size_t StrList_SearchLongestPrefix(const StrList *sl, const char *s, size_t s_len, char separator, bool direction_forward)
Definition: strlist.c:602
size_t StrList_Insert(StrList **sl, const char *s, size_t idx)
Definition: strlist.c:226
struct admitdeny_acl admit
Definition: server_access.h:72
struct admitdeny_acl deny
Definition: server_access.h:73
size_t len
Definition: server_access.h:67
StrList * resource_names
Definition: server_access.h:69
size_t alloc_len
Definition: server_access.h:68
struct acl::resource_acl acls[]
StrList * hostnames
Definition: server_access.h:47
StrList * keys
Definition: server_access.h:48
StrList * usernames
Definition: server_access.h:49
StrList * ips
Definition: server_access.h:46
char str[]
Definition: strlist.h:39
struct string * list[]
Definition: strlist.h:74