Skip to content

Commit

Permalink
Merge pull request bareos#1875
Browse files Browse the repository at this point in the history
Fix multiple ACL handling bugs
  • Loading branch information
BareosBot authored Jul 12, 2024
2 parents afbdb63 + af86d44 commit 2a02669
Show file tree
Hide file tree
Showing 42 changed files with 516 additions and 650 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# generated version files
cmake/BareosVersion.cmake

# python
__pycache__

# docs
docs/manuals/source/include/autogenerated/*.rst.inc
docs/manuals/source/include/autogenerated/autosummary/
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- fix warnings on FreeBSD 13.3 compiler [PR #1881]
- dir: fix crash on purge with job without client [PR #1857]
- fix runtime status [PR #1872]
- Fix multiple ACL handling bugs [PR #1875]

[PR #1538]: https://github.com/bareos/bareos/pull/1538
[PR #1581]: https://github.com/bareos/bareos/pull/1581
Expand Down Expand Up @@ -208,6 +209,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[PR #1865]: https://github.com/bareos/bareos/pull/1865
[PR #1868]: https://github.com/bareos/bareos/pull/1868
[PR #1872]: https://github.com/bareos/bareos/pull/1872
[PR #1875]: https://github.com/bareos/bareos/pull/1875
[PR #1878]: https://github.com/bareos/bareos/pull/1878
[PR #1881]: https://github.com/bareos/bareos/pull/1881
[PR #1883]: https://github.com/bareos/bareos/pull/1883
Expand Down
4 changes: 2 additions & 2 deletions core/src/dird/dird_conf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ static ResourceItem dir_items[] = {
{ "CatalogAcl", CFG_TYPE_ACL, ITEM(resource, ACL_lists), Catalog_ACL, 0, NULL, NULL,\
"Lists the Catalog resources, this resource has access to. The special keyword *all* allows access to all Catalog resources." },\
{ "WhereAcl", CFG_TYPE_ACL, ITEM(resource, ACL_lists), Where_ACL, 0, NULL, NULL,\
"Specifies the base directories, where files could be restored. An empty string allows restores to all directories." },\
"Specifies the base directories, where files could be restored." },\
{ "PluginOptionsAcl", CFG_TYPE_ACL, ITEM(resource, ACL_lists), PluginOptions_ACL, 0, NULL, NULL,\
"Specifies the allowed plugin options. An empty strings allows all Plugin Options." }

Expand Down Expand Up @@ -2790,7 +2790,7 @@ static void StoreAcl(LEX* lc, ResourceItem* item, int index, int pass)
LexGetToken(lc, BCT_STRING);
if (pass == 1) {
if (!IsAclEntryValid(lc->str, msg)) {
Emsg1(M_ERROR, 0, T_("Cannot store Acl: %s\n"), msg.data());
scan_err1(lc, T_("Cannot store Acl: %s"), msg.data());
return;
}
list->append(strdup(lc->str));
Expand Down
223 changes: 92 additions & 131 deletions core/src/dird/ua_acl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Copyright (C) 2004-2008 Free Software Foundation Europe e.V.
Copyright (C) 2014-2016 Planets Communications B.V.
Copyright (C) 2014-2022 Bareos GmbH & Co. KG
Copyright (C) 2014-2024 Bareos GmbH & Co. KG
This program is Free Software; you can redistribute it and/or
modify it under the terms of version three of the GNU Affero General Public
Expand Down Expand Up @@ -47,141 +47,104 @@ bool UaContext::AclAccessOk(int acl, const char* item, bool audit_event)
* A regexp uses the following chars:
* ., (, ), [, ], |, ^, $, +, ?, *
*/
static bool is_regex(std::string string_to_check)
constexpr bool is_regex(std::string_view string_to_check)
{
return std::string::npos != string_to_check.find_first_of(".()[]|^$+?*");
return string_to_check.npos != string_to_check.find_first_of(".()[]|^$+?*");
}

/**
* Loop over the items in the alist and verify if they match the given item
* that access was requested for.
* acl_list_value: value of the ACL definition.
* acl_list_compare_value: value ot compare against. This is identical to
* acl_list_value or a modified acl_list_value to compare against.
*/
static inline bool FindInAclList(alist<const char*>* list,
int acl,
const char* item,
int len)
static inline bool CompareAclListValueWithItem(
int acl,
const char* acl_list_value,
const char* acl_list_compare_value,
const char* item,
int item_length)
{
int rc;
regex_t preg{};
int nmatch = 1;
bool retval = false;
regmatch_t pmatch[1]{};
const char* list_value;
// gives full access
if (Bstrcasecmp("*all*", acl_list_compare_value)) {
Dmsg2(1400, "Global ACL found in %d %s\n", acl, acl_list_value);
return true;
}

// See if we have an empty list.
if (!list) {
/*
* Empty list for Where => empty where accept anything.
* For any other list, reject everything.
*/
if (len == 0 && acl == Where_ACL) {
Dmsg0(1400, "Empty Where_ACL allowing restore anywhere\n");
retval = true;
}
goto bail_out;
if (Bstrcasecmp(item, acl_list_compare_value)) {
// Explicit match.
Dmsg3(1400, "ACL found %s in %d %s\n", item, acl, acl_list_value);
return true;
}

// Search list for item
for (int i = 0; i < list->size(); i++) {
list_value = (char*)list->get(i);
/* If we didn't get an exact match see if we can use the pattern as a
* regex. */
if (is_regex(acl_list_compare_value)) {
regex_t preg{};
int rc = regcomp(&preg, acl_list_compare_value, REG_EXTENDED | REG_ICASE);
if (rc != 0) {
// Not a valid regular expression so skip it.
Dmsg1(1400, "Not a valid regex %s, ignoring for regex compare\n",
acl_list_value);
return false;
}

// See if this is a deny acl.
if (*list_value == '!') {
if (Bstrcasecmp(item, list_value + 1)) {
// Explicit deny.
Dmsg3(1400, "Deny ACL found %s in %d %s\n", item, acl, list_value);
goto bail_out;
int nmatch = 1;
regmatch_t pmatch[1]{};
if (regexec(&preg, item, nmatch, pmatch, 0) == 0) {
// Make sure its not a partial match but a full match.
Dmsg2(1400, "Found match start offset %d end offset %d\n",
pmatch[0].rm_so, pmatch[0].rm_eo);
if ((pmatch[0].rm_eo - pmatch[0].rm_so) >= item_length) {
Dmsg3(1400, "ACL found %s in %d using regex %s\n", item, acl,
acl_list_value);
regfree(&preg);
return true;
}
}
regfree(&preg);
}
return false;
}

/*
* If we didn't get an exact match see if we can use the pattern as a
* regex.
*/
if (is_regex(list_value + 1)) {
int match_length;

match_length = strlen(item);
rc = regcomp(&preg, list_value + 1, REG_EXTENDED | REG_ICASE);
if (rc != 0) {
// Not a valid regular expression so skip it.
Dmsg1(1400, "Not a valid regex %s, ignoring for regex compare\n",
list_value);
continue;
}

if (regexec(&preg, item, nmatch, pmatch, 0) == 0) {
// Make sure its not a partial match but a full match.
Dmsg2(1400, "Found match start offset %d end offset %d\n",
pmatch[0].rm_so, pmatch[0].rm_eo);
if ((pmatch[0].rm_eo - pmatch[0].rm_so) >= match_length) {
Dmsg3(1400, "ACL found %s in %d using regex %s\n", item, acl,
list_value);
regfree(&preg);
goto bail_out;
}
}
/**
* Loop over the items in the alist and verify if they match the given item
* that access was requested for.
*/
static inline std::optional<bool> FindInAclList(alist<const char*>* list,
int acl,
const char* item,
int item_length)
{
// See if we have an empty list.
if (!list || list->empty()) { return std::nullopt; }

regfree(&preg);
// Search list for item
const char* list_value = nullptr;
foreach_alist (list_value, list) {
// See if this is a deny acl.
if (*list_value == '!') {
if (CompareAclListValueWithItem(acl, list_value, list_value + 1, item,
item_length)) {
return false;
}
} else {
// gives full access
if (Bstrcasecmp("*all*", list_value)) {
Dmsg2(1400, "Global ACL found in %d %s\n", acl, list_value);
retval = true;
goto bail_out;
}

if (Bstrcasecmp(item, list_value)) {
Dmsg3(1400, "ACL found %s in %d %s\n", item, acl, list_value);
retval = true;
goto bail_out;
}

/*
* If we didn't get an exact match see if we can use the pattern as a
* regex.
*/
if (is_regex(list_value)) {
int match_length;

match_length = strlen(item);
rc = regcomp(&preg, list_value, REG_EXTENDED | REG_ICASE);
if (rc != 0) {
// Not a valid regular expression so skip it.
Dmsg1(1400, "Not a valid regex %s, ignoring for regex compare\n",
list_value);
continue;
}

if (regexec(&preg, item, nmatch, pmatch, 0) == 0) {
// Make sure its not a partial match but a full match.
Dmsg2(1400, "Found match start offset %d end offset %d\n",
pmatch[0].rm_so, pmatch[0].rm_eo);
if ((pmatch[0].rm_eo - pmatch[0].rm_so) >= match_length) {
Dmsg3(1400, "ACL found %s in %d using regex %s\n", item, acl,
list_value);
retval = true;
regfree(&preg);
goto bail_out;
}
}

regfree(&preg);
if (CompareAclListValueWithItem(acl, list_value, list_value, item,
item_length)) {
return true;
}
}
}

bail_out:
return retval;
return std::nullopt;
}

// This version expects the length of the item which we must check.
bool UaContext::AclAccessOk(int acl,
const char* item,
int len,
int item_length,
bool audit_event)
{
bool retval = false;
std::optional<bool> retval;

// The resource name contains nasty characters
switch (acl) {
Expand All @@ -203,27 +166,29 @@ bool UaContext::AclAccessOk(int acl,
goto bail_out;
}

retval = FindInAclList(user_acl->ACL_lists[acl], acl, item, len);
retval = FindInAclList(user_acl->ACL_lists[acl], acl, item, item_length);

/*
* If we didn't find a matching ACL try to use the profiles this console is
* connected to.
*/
if (!retval && user_acl->profiles && user_acl->profiles->size()) {
ProfileResource* profile = nullptr;
/* If we didn't find a matching ACL try to use the profiles this console is
* connected to. */
if (!retval.has_value()) {
if (user_acl->profiles && user_acl->profiles->size()) {
ProfileResource* profile = nullptr;

foreach_alist (profile, user_acl->profiles) {
retval = FindInAclList(profile->ACL_lists[acl], acl, item, len);
foreach_alist (profile, user_acl->profiles) {
retval = FindInAclList(profile->ACL_lists[acl], acl, item, item_length);

// If we found a match break the loop.
if (retval) { break; }
// If we found a match break the loop.
if (retval.has_value()) { break; }
}
}
}

bail_out:
if (audit_event && !retval) { LogAuditEventAclFailure(acl, item); }
if (audit_event && !retval.value_or(false)) {
LogAuditEventAclFailure(acl, item);
}

return retval;
return retval.value_or(false);
}

/**
Expand Down Expand Up @@ -306,11 +271,9 @@ bool UaContext::IsResAllowed(BareosResource* res)

acl = RcodeToAcltype(res->rcode_);
if (acl == -1) {
/*
* For all resources for which we don't know an explicit mapping
/* For all resources for which we don't know an explicit mapping
* to the right ACL we check if the Command ACL has access to the
* configure command just as we do for suppressing sensitive data.
*/
* configure command just as we do for suppressing sensitive data. */
return AclAccessOk(Command_ACL, "configure", false);
}

Expand All @@ -330,11 +293,9 @@ BareosResource* UaContext::GetResWithName(int rcode,

acl = RcodeToAcltype(rcode);
if (acl == -1) {
/*
* For all resources for which we don't know an explicit mapping
/* For all resources for which we don't know an explicit mapping
* to the right ACL we check if the Command ACL has access to the
* configure command just as we do for suppressing sensitive data.
*/
* configure command just as we do for suppressing sensitive data. */
if (!AclAccessOk(Command_ACL, "configure", false)) { goto bail_out; }
} else {
if (!AclAccessOk(acl, name, audit_event)) { goto bail_out; }
Expand Down
Loading

0 comments on commit 2a02669

Please sign in to comment.