diff --git a/.gitignore b/.gitignore index 1c65fed6636..3130dceb5f6 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 341fe1513e0..a556f188e55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/core/src/dird/dird_conf.cc b/core/src/dird/dird_conf.cc index f1690fcec57..c9ea4b7ab7d 100644 --- a/core/src/dird/dird_conf.cc +++ b/core/src/dird/dird_conf.cc @@ -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." } @@ -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)); diff --git a/core/src/dird/ua_acl.cc b/core/src/dird/ua_acl.cc index a1a5f8a867a..a9345fe00b2 100644 --- a/core/src/dird/ua_acl.cc +++ b/core/src/dird/ua_acl.cc @@ -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 @@ -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* 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 FindInAclList(alist* 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 retval; // The resource name contains nasty characters switch (acl) { @@ -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); } /** @@ -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); } @@ -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; } diff --git a/core/src/dird/ua_cmds.cc b/core/src/dird/ua_cmds.cc index 3ba292814bb..2a748d17b9b 100644 --- a/core/src/dird/ua_cmds.cc +++ b/core/src/dird/ua_cmds.cc @@ -177,9 +177,9 @@ static struct ua_cmdstruct commands[] = { {NT_(".api"), DotApiCmd, T_("Switch between different api modes"), NT_("[ 0 | 1 | 2 | off | on | json ] [compact=]"), false, false}, {NT_(".authorized"), DotAuthorizedCmd, T_("Check for authorization"), - NT_("job= | client= | storage= | pool= | cmd= | \n" - "fileset= | catalog="), + NT_("job= | client= | storage= " + "| schedule= | pool= | cmd= " + "| fileset= | catalog="), false, false}, {NT_(".catalogs"), DotCatalogsCmd, T_("List all catalog resources"), NULL, false, false}, @@ -224,9 +224,9 @@ static struct ua_cmdstruct commands[] = { NT_("[enabled | disabled]"), false, false}, {NT_(".status"), DotStatusCmd, T_("Report status"), NT_("dir ( current | last | header | scheduled | running | terminated ) " - "|\n" + "| " "storage= [ header | waitreservation | devices | volumes | " - "spooling | running | terminated ] |\n" + "spooling | running | terminated ] | " "client= [ header | terminated | running ]"), false, true}, {NT_(".storages"), DotStorageCmd, T_("List all storage resources"), @@ -303,13 +303,12 @@ static struct ua_cmdstruct commands[] = { "mode"), NT_("on | off"), false, false}, {NT_("help"), help_cmd, T_("Print help on specific command"), - NT_("add autodisplay automount cancel configure create delete disable\n" - "\tenable estimate exit gui label list llist\n" - "\tmessages memory mount prune purge quit query\n" - "\trestore relabel release reload run status\n" - "\tsetbandwidth setdebug setip show sqlquery time trace truncate " - "unmount\n" - "\tumount update use var version wait"), + NT_("add autodisplay automount cancel configure create delete disable " + "enable estimate exit gui label list llist " + "messages memory mount prune purge quit query " + "restore relabel release reload run status " + "setbandwidth setdebug setip show sqlquery time trace truncate " + "unmount umount update use var version wait"), false, false}, {NT_("import"), ImportCmd, T_("Import volumes from import/export slots to normal slots"), @@ -321,77 +320,76 @@ static struct ua_cmdstruct commands[] = { "slot= [ drive = ] [ barcodes ] [ encrypt ] [ yes ]"), false, true}, {NT_("list"), list_cmd, T_("List objects from catalog"), - NT_("basefiles jobid= | basefiles ujobid= |\n" + NT_("basefiles jobid= | basefiles ujobid= | " "backups client= [fileset=] " "[jobstatus=] [level=] [order=] " - "[limit=] |\n" - "clients | copies jobid= |\n" - "files jobid= | files ujobid= |\n" - "filesets |\n" - "fileset [ jobid= ] | fileset [ ujobid= ] |\n" - "fileset [ filesetid= ] | fileset [ jobid= ] |\n" + "[limit=] | " + "clients | copies jobid= | " + "files jobid= | files ujobid= | " + "filesets | " + "fileset [ jobid= ] | fileset [ ujobid= ] | " + "fileset [ filesetid= ] | fileset [ jobid= ] | " "jobs [job=] [client=] [jobstatus=] " "[jobtype=] [joblevel=] [volume=] " "[pool=] " - "[days=] [hours=] [last] [count] |\n" + "[days=] [hours=] [last] [count] | " "job= [client=] [jobstatus=] " "[jobtype=] [volume=] [days=] " - "[hours=] |\n" - "jobid= | ujobid= |\n" - "joblog jobid= | joblog ujobid= |\n" - "jobmedia jobid= | jobmedia ujobid= |\n" - "jobtotals |\n" - "jobstatistics jobid= |\n" - "log [ limit= [ offset= ] ] [reverse]|\n" + "[hours=] | " + "jobid= | ujobid= | " + "joblog jobid= | joblog ujobid= | " + "jobmedia jobid= | jobmedia ujobid= | " + "jobtotals | " + "jobstatistics jobid= | " + "log [ limit= [ offset= ] ] [reverse]| " "media [ jobid= | ujobid= | pool= | " - "all ] |\n" - "media= |\n" - "nextvol job= | nextvolume ujobid= |\n" - "pools |\n" - "pool= |\n" - "poolid= |\n" - "storages |\n" + "all ] | " + "media= | " + "nextvol job= | nextvolume ujobid= | " + "pools | " + "pool= | " + "poolid= | " + "storages | " "volumes [ jobid= | ujobid= | pool= " - "| all ] [count] |\n" - "volume= |\n" - "volumeid= | mediaid= |\n" - "[current] | [enabled | disabled] |\n" + "| all ] [count] | " + "volume= | " + "volumeid= | mediaid= | " + "[current] | [enabled | disabled] | " "[limit= [offset=]]"), true, true}, {NT_("llist"), LlistCmd, T_("Full or long list like list command"), - NT_("basefiles jobid= | basefiles ujobid= |\n" + NT_("basefiles jobid= | basefiles ujobid= | " "backups client= [fileset=] " "[jobstatus=] [level=] [order=] " - "[limit=] [days=] [hours=]|\n" - "clients | copies jobid= |\n" - "files jobid= | files ujobid= |\n" - "filesets |\n" - "fileset jobid= | fileset ujobid= |\n" - "fileset [ filesetid= ] | fileset [ jobid= ] |\n" + "[limit=] [days=] [hours=] | " + "clients | copies jobid= | " + "files jobid= | files ujobid= | " + "filesets | " + "fileset jobid= | fileset ujobid= | " + "fileset [ filesetid= ] | fileset [ jobid= ] | " "jobs [job=] [client=] [jobstatus=] " "[jobtype=] [volume=] [pool=] " "[days=] " - "[hours=] [last] [count] |\n" + "[hours=] [last] [count] | " "job= [client=] [jobstatus=] " "[jobtype=] [joblevel=] [volume=] " - "[days=] [hours=] |\n" - "jobid= | ujobid= |\n" + "[days=] [hours=] | " + "jobid= | ujobid= | " "joblog jobid= [count] | joblog ujobid= [count] " - "|\n" - "jobmedia jobid= | jobmedia ujobid= |\n" - "jobtotals |\n" - "media [ jobid= | ujobid= | pool= | " - "all ] |\n" - "media= |\n" - "nextvol job= | nextvolume ujobid= |\n" - "pools |\n" - "pool= |\n" - "poolid= |\n" + "| jobmedia jobid= | jobmedia ujobid= " + "| jobtotals " + "| media [ jobid= | ujobid= | pool= " + "| all ] " + "| media= | " + "nextvol job= | nextvolume ujobid= | " + "pools | " + "pool= | " + "poolid= | " "volumes [ jobid= | ujobid= | pool= " - "| all ] [count] |\n" - "volume= |\n" - "volumeid= | mediaid= |\n" - "[current] | [enabled | disabled] |\n" + "| all ] [count] | " + "volume= | " + "volumeid= | mediaid= | " + "[current] | [enabled | disabled] | " "[limit= [offset=]]"), true, true}, {NT_("messages"), MessagesCmd, T_("Display pending messages"), NT_(""), @@ -399,27 +397,27 @@ static struct ua_cmdstruct commands[] = { {NT_("memory"), MemoryCmd, T_("Print current memory usage"), NT_(""), true, false}, {NT_("mount"), MountCmd, T_("Mount storage"), - NT_("storage= slot= drive=\n" - "\tjobid= | job= | ujobid="), + NT_("storage= slot= drive= " + "jobid= | job= | ujobid="), false, true}, {NT_("move"), move_cmd, T_("Move slots in an autochanger"), NT_("storage= srcslots= " "dstslots="), true, true}, {NT_("prune"), PruneCmd, T_("Prune records from catalog"), - NT_("files [client=] [pool=] [yes] |\n" - "jobs [client=] [pool=] [yes] |\n" - "volume [all] [=volume] [pool=] [yes] |\n" - "stats [yes] |\n" - "directory [=directory] [client=] [recursive] [yes] |\n"), + NT_("files [client=] [pool=] [yes] | " + "jobs [client=] [pool=] [yes] | " + "volume [all] [=volume] [pool=] [yes] | " + "stats [yes] | " + "directory [=directory] [client=] [recursive] [yes]"), true, true}, {NT_("purge"), PurgeCmd, T_("Purge records from catalog"), NT_("[files [job= | jobid= | client= | " - "volume=]] |\n" + "volume=]] | " "[jobs [client= | volume=] | pool=] " - "[jobstatus=]] |\n" + "[jobstatus=]] | " "[volume[=] [storage=] [pool= | allpools] " - "[devicetype=] [drive=] [action=]] |\n" + "[devicetype=] [drive=] [action=]] | " "[quota [client=]]"), true, true}, {NT_("quit"), quit_cmd, T_("Terminate Bconsole session"), NT_(""), false, @@ -427,21 +425,21 @@ static struct ua_cmdstruct commands[] = { {NT_("query"), QueryCmd, T_("Query catalog"), NT_(""), false, true}, {NT_("restore"), RestoreCmd, T_("Restore files"), NT_("where= client= storage= " - "bootstrap=\n" - "\trestorejob= comment= jobid= " - "fileset=\n" - "\treplace= " - "pluginoptions=\n" - "\tregexwhere= fileregex= " - "restoreclient= backupformat=\n" - "\tpool= file= directory= " - "before=\n" - "\tstrip_prefix= add_prefix= add_suffix=\n" - "\tselect= select before current copies done all"), + "bootstrap= " + "restorejob= comment= jobid= " + "fileset= " + "replace= " + "pluginoptions= " + "regexwhere= fileregex= " + "restoreclient= backupformat= " + "pool= file= directory= " + "before= " + "strip_prefix= add_prefix= add_suffix= " + "select= select before current copies done all"), false, true}, {NT_("relabel"), RelabelCmd, T_("Relabel a tape"), - NT_("storage= oldvolume=\n" - "\tvolume= pool= [ encrypt ]"), + NT_("storage= oldvolume= " + " volume= pool= [ encrypt ]"), false, true}, {NT_("release"), ReleaseCmd, T_("Release storage"), NT_("storage= [ drive= ] [ alldrives ]"), true, @@ -456,29 +454,29 @@ static struct ua_cmdstruct commands[] = { true}, {NT_("run"), RunCmd, T_("Run a job"), NT_("job= client= fileset= " - "level=\n" - "\tstorage= where= " - "when=\n" - "\tpool= pluginoptions= " - "accurate= comment=\n" - "\tspooldata= priority= jobid= " - "catalog= migrationjob=\n" - "\tbackupclient= backupformat= " - "nextpool=\n" - "\tsince= verifyjob= " - "verifylist=\n" - "\tmigrationjob= yes"), + "level= " + "storage= where= " + "when= " + "pool= pluginoptions= " + "accurate= comment= " + "spooldata= priority= jobid= " + "catalog= migrationjob= " + "backupclient= backupformat= " + "nextpool= " + "since= verifyjob= " + "verifylist= " + "migrationjob= [ yes ]"), false, true}, {NT_("status"), StatusCmd, T_("Report status"), NT_("all | dir= | director | scheduler | " - "schedule= | client= |\n" - "storage= slots | days= | job= |\n" + "schedule= | client= | " + "storage= slots | days= | job= | " "subscriptions [detail] [unknown] [all] | configuration"), true, true}, {NT_("setbandwidth"), SetbwlimitCmd, T_("Sets bandwidth"), - NT_("client= | storage= | jobid= |\n" - "\tjob= | ujobid= state= | all\n" - "\tlimit= [ yes ]"), + NT_("[ client= | storage= | jobid= " + "| job= | ujobid= state= | all ] " + "limit= [ yes ]"), true, true}, {NT_("setdebug"), SetdebugCmd, T_("Sets debug level"), NT_("level= trace=0/1 timestamp=0/1 client= | dir | " @@ -497,14 +495,11 @@ static struct ua_cmdstruct commands[] = { "message= | " "pool= | profile= | " "schedule= | storage= " - "|\n" - "catalog | clients | consoles | directors | filesets | jobdefs | jobs " - "| " - "messages | pools | profiles | schedules | storages " - "|\n" - "disabled [ clients | jobs | schedules ] " - "|\n" - "all [verbose]"), + "| catalog | clients | consoles | directors | filesets " + "| jobdefs | jobs " + "| messages | pools | profiles | schedules | storages " + "| disabled [ clients | jobs | schedules ] " + "| all [verbose]"), true, true}, {NT_("sqlquery"), SqlqueryCmd, T_("Use SQL to query catalog"), NT_(""), false, true}, @@ -517,13 +512,13 @@ static struct ua_cmdstruct commands[] = { "[yes]"), true, true}, {NT_("unmount"), UnmountCmd, T_("Unmount storage"), - NT_("storage= [ drive= ]\n" - "\tjobid= | job= | ujobid="), + NT_("storage= [ drive= ] " + "jobid= | job= | ujobid="), false, true}, {NT_("umount"), UnmountCmd, T_("Umount - for old-time Unix guys, see unmount"), - NT_("storage= [ drive= ]\n" - "\tjobid= | job= | ujobid="), + NT_("storage= [ drive= ] " + "jobid= | job= | ujobid="), false, true}, {NT_("update"), UpdateCmd, T_("Update volume, pool, slots, job or statistics"), @@ -532,19 +527,15 @@ static struct ua_cmdstruct commands[] = { "actiononpurge=] " "[pool=] [recycle=] [slot=] " "[inchanger=]] " - "|\n" - "[pool= " + "| [pool= " "[maxvolbytes=] [maxvolfiles=] [maxvoljobs=]" "[enabled=] [recyclepool=] " - "[actiononpurge=] |\n" + "[actiononpurge=] | " "slots [storage=] [scan]] " - "|\n" - "[jobid= [jobname=] [starttime=] " - "[client=]\n" + "| [jobid= [jobname=] [starttime=] " + "[client=] " "[filesetid=] [jobtype=]] " - "|\n" - "[stats " - "[days=]]"), + "| [stats [days=]]"), true, true}, {NT_("use"), use_cmd, T_("Use specific catalog"), NT_("catalog="), false, true}, @@ -587,16 +578,17 @@ bool Do_a_command(UaContext* ua) len = strlen(ua->argk[0]); for (i = 0; i < comsize; i++) { /* search for command */ if (bstrncasecmp(ua->argk[0], commands[i].key, len)) { - // Check if command permitted, but "quit" and "whoami" is always OK - if (!bstrcmp(ua->argk[0], NT_("quit")) - && !bstrcmp(ua->argk[0], NT_("whoami")) - && !ua->AclAccessOk(Command_ACL, ua->argk[0], true)) { + const char* command = commands[i].key; + // Check if command permitted, but "exit/quit" and "whoami" is always OK + if (!bstrcmp(command, NT_("exit")) && !bstrcmp(command, NT_("quit")) + && !bstrcmp(command, NT_("whoami")) + && !ua->AclAccessOk(Command_ACL, command, true)) { break; } // Check if this command is authorized in RunScript if (ua->runscript && !commands[i].allowed_in_runscript) { - ua->ErrorMsg(T_("Can't use %s command in a runscript"), ua->argk[0]); + ua->ErrorMsg(T_("Can't use %s command in a runscript"), command); break; } diff --git a/core/src/dird/ua_run.cc b/core/src/dird/ua_run.cc index 697ab29a909..4c8eef73df0 100644 --- a/core/src/dird/ua_run.cc +++ b/core/src/dird/ua_run.cc @@ -2,7 +2,7 @@ Copyright (C) 2001-2012 Free Software Foundation Europe e.V. Copyright (C) 2011-2016 Planets Communications B.V. - Copyright (C) 2013-2023 Bareos GmbH & Co. KG + Copyright (C) 2013-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 @@ -49,6 +49,7 @@ static bool DisplayJobParameters(UaContext* ua, JobControlRecord* jcr, RunContext& rc); static void SelectWhereRegexp(UaContext* ua, JobControlRecord* jcr); +static bool GetPluginOptions(UaContext* ua, JobControlRecord* jcr); static bool ScanCommandLineArguments(UaContext* ua, RunContext& rc); static bool ResetRestoreContext(UaContext* ua, JobControlRecord* jcr, @@ -723,29 +724,29 @@ int ModifyJobParameters(UaContext* ua, JobControlRecord* jcr, RunContext& rc) } else if (jcr->is_JobType(JT_RESTORE)) { /* Where */ if (GetCmd(ua, T_("Please enter the full path prefix for restore (/ " "for none): "))) { - if (jcr->RegexWhere) { /* cannot use regexwhere and where */ - free(jcr->RegexWhere); - jcr->RegexWhere = NULL; - } - if (jcr->where) { - free(jcr->where); - jcr->where = NULL; - } - if (IsPathSeparator(ua->cmd[0]) && ua->cmd[1] == '\0') { - ua->cmd[0] = 0; + if (!ua->AclAccessOk(Where_ACL, ua->cmd, true)) { + ua->SendMsg( + T_("No authorization for \"where\" specification.\n")); + } else { + if (jcr->RegexWhere) { /* cannot use regexwhere and where */ + free(jcr->RegexWhere); + jcr->RegexWhere = NULL; + } + if (jcr->where) { + free(jcr->where); + jcr->where = NULL; + } + // "/" is treated as no prefix. + if (IsPathSeparator(ua->cmd[0]) && ua->cmd[1] == '\0') { + ua->cmd[0] = 0; + } + jcr->where = strdup(ua->cmd); } - jcr->where = strdup(ua->cmd); goto try_again; } } else { /* Plugin Options */ - if (GetCmd(ua, T_("Please enter Plugin Options string: "))) { - if (jcr->dir_impl->plugin_options) { - free(jcr->dir_impl->plugin_options); - jcr->dir_impl->plugin_options = NULL; - } - jcr->dir_impl->plugin_options = strdup(ua->cmd); - goto try_again; - } + GetPluginOptions(ua, jcr); + goto try_again; } break; case 10: @@ -754,14 +755,8 @@ int ModifyJobParameters(UaContext* ua, JobControlRecord* jcr, RunContext& rc) SelectWhereRegexp(ua, jcr); goto try_again; } else if (jcr->is_JobType(JT_BACKUP)) { - if (GetCmd(ua, T_("Please enter Plugin Options string: "))) { - if (jcr->dir_impl->plugin_options) { - free(jcr->dir_impl->plugin_options); - jcr->dir_impl->plugin_options = NULL; - } - jcr->dir_impl->plugin_options = strdup(ua->cmd); - goto try_again; - } + GetPluginOptions(ua, jcr); + goto try_again; } break; case 11: @@ -788,14 +783,8 @@ int ModifyJobParameters(UaContext* ua, JobControlRecord* jcr, RunContext& rc) goto try_again; case 13: /* Plugin Options */ - if (GetCmd(ua, T_("Please enter Plugin Options string: "))) { - if (jcr->dir_impl->plugin_options) { - free(jcr->dir_impl->plugin_options); - jcr->dir_impl->plugin_options = NULL; - } - jcr->dir_impl->plugin_options = strdup(ua->cmd); - goto try_again; - } + GetPluginOptions(ua, jcr); + goto try_again; break; case -1: /* error or cancel */ return -1; @@ -1059,6 +1048,10 @@ static void SelectWhereRegexp(UaContext* ua, JobControlRecord* jcr) case 3: /* Add rwhere */ if (GetCmd(ua, T_("Please enter a valid regexp (!from!to!): "))) { + if (!ua->AclAccessOk(Where_ACL, ua->cmd, true)) { + ua->SendMsg(T_("Denied by \"WhereACL\" configuration.\n")); + goto try_again_reg; + } if (rwhere) free(rwhere); rwhere = strdup(ua->cmd); } @@ -1125,6 +1118,13 @@ static void SelectWhereRegexp(UaContext* ua, JobControlRecord* jcr) jcr->RegexWhere = (char*)malloc(len * sizeof(char)); bregexp_build_where(jcr->RegexWhere, len, strip_prefix, add_prefix, add_suffix); + if (!ua->AclAccessOk(Where_ACL, jcr->RegexWhere, true)) { + ua->SendMsg(T_("Regex (%s) denied by \"WhereACL\" configuration.\n"), + jcr->RegexWhere); + free(jcr->RegexWhere); + jcr->RegexWhere = NULL; + goto try_again_reg; + } } regs = get_bregexps(jcr->RegexWhere); @@ -1146,6 +1146,24 @@ static void SelectWhereRegexp(UaContext* ua, JobControlRecord* jcr) if (rwhere) { free(rwhere); } } +static bool GetPluginOptions(UaContext* ua, JobControlRecord* jcr) +{ + if (GetCmd(ua, T_("Please enter Plugin Options string: "))) { + if (!ua->AclAccessOk(PluginOptions_ACL, ua->cmd, true)) { + ua->SendMsg( + T_("No authorization for \"PluginOptions\" specification.\n")); + return false; + } + if (jcr->dir_impl->plugin_options) { + free(jcr->dir_impl->plugin_options); + jcr->dir_impl->plugin_options = NULL; + } + jcr->dir_impl->plugin_options = strdup(ua->cmd); + return true; + } + return false; +} + static void SelectJobLevel(UaContext* ua, JobControlRecord* jcr) { if (jcr->is_JobType(JT_BACKUP)) { @@ -1606,16 +1624,16 @@ static bool DisplayJobParameters(UaContext* ua, } else { if (ua->api) ua->signal(BNET_RUN_CMD); ua->SendMsg(T_("Run Restore job\n" - "JobName: %s\n" - "Bootstrap: %s\n"), + "JobName: %s\n" + "Bootstrap: %s\n"), job->resource_name_, NPRT(jcr->RestoreBootstrap)); /* RegexWhere is take before RestoreWhere */ if (jcr->RegexWhere || (job->RegexWhere && !jcr->where)) { - ua->SendMsg(T_("RegexWhere: %s\n"), + ua->SendMsg(T_("RegexWhere: %s\n"), jcr->RegexWhere ? jcr->RegexWhere : job->RegexWhere); } else { - ua->SendMsg(T_("Where: %s\n"), + ua->SendMsg(T_("Where: %s\n"), jcr->where ? jcr->where : NPRT(job->RestoreWhere)); } @@ -1880,7 +1898,8 @@ static bool ScanCommandLineArguments(UaContext* ua, RunContext& rc) } rc.where = ua->argv[i]; if (!ua->AclAccessOk(Where_ACL, rc.where, true)) { - ua->SendMsg(T_("No authoriztion for \"where\" specification.\n")); + ua->SendMsg( + T_("No authorization for \"where\" specification.\n")); return false; } kw_ok = true; diff --git a/docs/manuals/source/include/autogenerated/bareos-dir-config-schema.json b/docs/manuals/source/include/autogenerated/bareos-dir-config-schema.json index 51a702f2b54..db1d8a4ff8b 100644 --- a/docs/manuals/source/include/autogenerated/bareos-dir-config-schema.json +++ b/docs/manuals/source/include/autogenerated/bareos-dir-config-schema.json @@ -2384,7 +2384,7 @@ "datatype": "ACL", "code": 8, "equals": true, - "description": "Specifies the base directories, where files could be restored. An empty string allows restores to all directories." + "description": "Specifies the base directories, where files could be restored." }, "PluginOptionsAcl": { "datatype": "ACL", @@ -2463,7 +2463,7 @@ "datatype": "ACL", "code": 8, "equals": true, - "description": "Specifies the base directories, where files could be restored. An empty string allows restores to all directories." + "description": "Specifies the base directories, where files could be restored." }, "PluginOptionsAcl": { "datatype": "ACL", @@ -2651,7 +2651,7 @@ "datatype": "ACL", "code": 8, "equals": true, - "description": "Specifies the base directories, where files could be restored. An empty string allows restores to all directories." + "description": "Specifies the base directories, where files could be restored." }, "PluginOptionsAcl": { "datatype": "ACL", diff --git a/docs/manuals/source/manually_added_config_directive_descriptions/dir-console-WhereAcl.rst.inc b/docs/manuals/source/manually_added_config_directive_descriptions/dir-console-WhereAcl.rst.inc index ce1da0b0a45..6a32c1a2479 100644 --- a/docs/manuals/source/manually_added_config_directive_descriptions/dir-console-WhereAcl.rst.inc +++ b/docs/manuals/source/manually_added_config_directive_descriptions/dir-console-WhereAcl.rst.inc @@ -1,2 +1,9 @@ -This directive permits you to specify where a restricted console can restore files. If this directive is not specified, only the default restore location is permitted (normally :file:`/tmp/bareos-restores`. If :strong:`*all*` is specified any path the user enters will be accepted. Any other value specified (there may be multiple :strong:`Where ACL`\ directives) will restrict the user to use that path. For example, on a Unix system, if you specify -"/", the file will be restored to the original location. +This directive permits you to specify where a restricted console can restore files. +This ACL is only evaluated when changing the default restore path +specified in the restore job (typically :file:`/tmp/bareos-restores`). +If :strong:`*all*` is specified any path the user enters will be accepted. +Any other value specified +(there may be multiple :strong:`Where ACL`\ directives) +will restrict the user to use that path. +For example, on a Unix system, if you specify "/", +the file will be restored to the original location. diff --git a/python-bareos/.gitignore b/python-bareos/.gitignore index c15b891fec1..effc9a17f9f 100644 --- a/python-bareos/.gitignore +++ b/python-bareos/.gitignore @@ -16,7 +16,6 @@ develop-eggs .installed.cfg lib lib64 -__pycache__ # Installer logs pip-log.txt diff --git a/systemtests/python-modules/bareos_unittest/json.py b/systemtests/python-modules/bareos_unittest/json.py index 98ab5eb0b9c..7798cb74f18 100644 --- a/systemtests/python-modules/bareos_unittest/json.py +++ b/systemtests/python-modules/bareos_unittest/json.py @@ -2,7 +2,7 @@ # # BAREOS - Backup Archiving REcovery Open Sourced # -# Copyright (C) 2019-2022 Bareos GmbH & Co. KG +# Copyright (C) 2019-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 @@ -51,12 +51,20 @@ def check_resource(director, resourcesname, name): logger = logging.getLogger() rc = False try: - result = director.call(u".{}".format(resourcesname)) - for i in result[resourcesname]: - if i["name"] == name: - rc = True + # . only returns one result key, + # but that can differ slightly from the called command, + # e.g. ".console" returns + # "result": { "consoles": [ ... ] } + # Therefore we check "all" keys of result + # and do a substring match. + result = director.call(".{}".format(resourcesname)) + for resourcetype in result.keys(): + if resourcetype.startswith(resourcesname): + for i in result[resourcetype]: + if i["name"] == name: + rc = True except Exception as e: - logger.warn(str(e)) + logger.warning(str(e)) return rc @staticmethod @@ -73,12 +81,10 @@ def configure_add(self, director, resourcesname, resourcename, cmd): self.assertEqual(result["configure"]["add"]["name"], resourcename) self.assertTrue( self.check_resource(director, resourcesname, resourcename), - u"Failed to find resource {} in {}.".format( - resourcename, resourcesname - ), + "Failed to find resource {} in {}.".format(resourcename, resourcesname), ) - def wait_job(self, director, jobId, expected_status=u"OK"): + def wait_job(self, director, jobId, expected_status="OK"): result = director.call("wait jobid={}".format(jobId)) # "result": { # "job": { @@ -90,14 +96,14 @@ def wait_job(self, director, jobId, expected_status=u"OK"): # } self.assertEqual(result["job"]["jobstatuslong"], expected_status) - def run_job(self, director, jobname=None, level=None, extra=None, wait=False): + def run_job(self, director, jobname, level=None, extra=None, wait=False): logger = logging.getLogger() run_parameter = ["job={}".format(jobname), "yes"] if level: - run_parameter.append(u"level={}".format(level)) + run_parameter.append("level={}".format(level)) if extra: - run_parameter.append(u"{}".format(extra)) - result = director.call("run {}".format(u" ".join(run_parameter))) + run_parameter.append("{}".format(extra)) + result = director.call("run {}".format(" ".join(run_parameter))) jobId = result["run"]["jobid"] if wait: self.wait_job(director, jobId) @@ -115,29 +121,45 @@ def run_restore( ): logger = logging.getLogger() run_parameter = ["client={}".format(client)] - if jobname: - run_parameter.append(u"restorejob={}".format(jobname)) + run_parameter.append("restorejob={}".format(jobname)) if jobid: - run_parameter.append(u"jobid={}".format(jobid)) + run_parameter.append("jobid={}".format(jobid)) if fileset: - run_parameter.append(u"fileset={}".format(fileset)) + run_parameter.append("fileset={}".format(fileset)) if extra: - run_parameter.append(u"{}".format(extra)) + run_parameter.append("{}".format(extra)) run_parameter += ["select", "all", "done", "yes"] - result = director.call("restore {}".format(u" ".join(run_parameter))) + result = director.call("restore {}".format(" ".join(run_parameter))) jobId = result["run"]["jobid"] if wait: self.wait_job(director, jobId) return jobId + def get_backup_jobid(self, director, jobname, level="F", extra=None): + """ + Get a valid backup job jobid. + If no such job exists, + run such a job. + """ + result = director.call( + "list jobs job={} level={} jobstatus=T last".format(jobname, level) + ) + try: + jobid = result["jobs"][0]["jobid"] + return jobid + except IndexError: + # there is no valid backup for these parameters. + # run a backup job. + return self.run_job(director, jobname, level, extra, wait=True) + def _test_job_result(self, jobs, jobid): logger = logging.getLogger() for job in jobs: if job["jobid"] == jobid: files = int(job["jobfiles"]) - logger.debug(u"Job {} contains {} files.".format(jobid, files)) - self.assertTrue(files >= 1, u"Job {} contains no files.".format(jobid)) + logger.debug("Job {} contains {} files.".format(jobid, files)) + self.assertTrue(files >= 1, "Job {} contains no files.".format(jobid)) return True self.fail("Failed to find job {}".format(jobid)) # add return to prevent pylint warning @@ -184,7 +206,7 @@ def _test_list_with_invalid_jobid(self, director, jobid): self.assertEqual( len(result), 0, - u"Command {} should not return results. Current result: {} visible".format( + "Command {} should not return results. Current result: {} visible".format( listcmd, str(result) ), ) @@ -222,4 +244,4 @@ def list_jobid(self, director, jobid): try: return result["jobs"][0] except KeyError: - raise ValueError(u"jobid {} does not exist".format(jobid)) + raise ValueError("jobid {} does not exist".format(jobid)) diff --git a/systemtests/tests/CMakeLists.txt b/systemtests/tests/CMakeLists.txt index 50bc5aa53ae..0c38be74288 100644 --- a/systemtests/tests/CMakeLists.txt +++ b/systemtests/tests/CMakeLists.txt @@ -37,7 +37,6 @@ add_subdirectory(chflags) add_subdirectory(client-initiated) add_subdirectory(commandline-options) add_subdirectory(config-dump) -add_subdirectory(config-syntax-crash) add_subdirectory(copy-migrate) add_subdirectory(copy-archive-job) add_subdirectory(copy-remote-bscan) diff --git a/systemtests/tests/config-syntax-crash/CMakeLists.txt b/systemtests/tests/config-syntax-crash/CMakeLists.txt deleted file mode 100644 index cfd54bc5868..00000000000 --- a/systemtests/tests/config-syntax-crash/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# BAREOSĀ® - Backup Archiving REcovery Open Sourced -# -# Copyright (C) 2021-2021 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 -# License as published by the Free Software Foundation and included -# in the file LICENSE. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -# 02110-1301, USA. - -get_filename_component(BASENAME ${CMAKE_CURRENT_BINARY_DIR} NAME) -create_systemtest(${SYSTEMTEST_PREFIX} ${BASENAME}) diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in deleted file mode 100644 index 3c3385945f0..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/catalog/MyCatalog.conf.in +++ /dev/null @@ -1,6 +0,0 @@ -Catalog { - Name = MyCatalog - dbname = "@db_name@" - dbuser = "@db_user@" - dbpassword = "@db_password@" -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in deleted file mode 100644 index 59b61700966..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/client/bareos-fd.conf.in +++ /dev/null @@ -1,7 +0,0 @@ -Client { - Name = bareos-fd - Description = "Client resource of the Director itself." - Address = @hostname@ - Password = "@fd_password@" # password for FileDaemon - FD PORT = @fd_port@ -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in deleted file mode 100644 index 2bc45b7dd10..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/director/bareos-dir.conf.in +++ /dev/null @@ -1,23 +0,0 @@ -Director { # define myself - Name = bareos-dir - QueryFile = "@scriptdir@/query.sql" - Maximum Concurrent Jobs = 10 - Password = "@dir_password@" # Console password - Messages = Daemon - Auditing = yes - - # Enable the Heartbeat if you experience connection losses - # (eg. because of your router or firewall configuration). - # Additionally the Heartbeat can be enabled in bareos-sd and bareos-fd. - # - # Heartbeat Interval = 1 min - - # remove comment from "Plugin Directory" to load plugins from specified directory. - # if "Plugin Names" is defined, only the specified plugins will be loaded, - # otherwise all director plugins (*-dir.so) from the "Plugin Directory". - # - # Plugin Directory = "@python_plugin_module_src_dir@" - # Plugin Names = "" - Working Directory = "@working_dir@" - DirPort = @dir_port@ -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in deleted file mode 100644 index 1dbd17581cf..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/fileset/Catalog.conf.in +++ /dev/null @@ -1,11 +0,0 @@ -FileSet { - Name = "Catalog" - Description = "Backup the catalog dump and Bareos configuration files." - Include { - Options { - Signature = XXH128 - } - File = "@working_dir@/@db_name@.sql" # database dump - File = "@confdir@" # configuration - } -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in deleted file mode 100644 index e2acb53b583..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/fileset/SelfTest.conf.in +++ /dev/null @@ -1,11 +0,0 @@ -FileSet { - Name = "SelfTest" - Description = "fileset just to backup some files for selftest" - Include { - Options { - Signature = XXH128 - } - #File = "@sbindir@" - File=<@tmpdir@/file-list - } -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in deleted file mode 100644 index 89256864d9a..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/job/RestoreFiles.conf.in +++ /dev/null @@ -1,11 +0,0 @@ -Job { - Name = "RestoreFiles" - Description = "Standard Restore template. Only one such job is needed for all standard Jobs/Clients/Storage ..." - Type = Restore - Client = bareos-fd - FileSet = SelfTest - Storage = File - Pool = Incremental - Messages = Standard - Where = @tmp@/bareos-restores -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf deleted file mode 100644 index ca1891f9620..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/job/backup-bareos-fd.conf +++ /dev/null @@ -1,5 +0,0 @@ -Job { - Name = "backup-bareos-fd" - JobDefs = "DefaultJob" - Client = "bareos-fd" -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in deleted file mode 100644 index 563126477c9..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/jobdefs/DefaultJob.conf.in +++ /dev/null @@ -1,15 +0,0 @@ -JobDefs { - Name = "DefaultJob" - Type = Backup - Level = Incremental - Client = bareos-fd - FileSet = "SelfTest" - Storage = File - Messages = Standard - Pool = Incremental - Priority = 10 - Write Bootstrap = "@working_dir@/%c.bsr" - Full Backup Pool = Full # write Full Backups into "Full" Pool - Differential Backup Pool = Differential # write Diff Backups into "Differential" Pool - Incremental Backup Pool = Incremental # write Incr Backups into "Incremental" Pool -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/messages/Daemon.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/messages/Daemon.conf.in deleted file mode 100644 index cf6a8cfa1e2..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/messages/Daemon.conf.in +++ /dev/null @@ -1,7 +0,0 @@ -Messages { - Name = Daemon - Description = "Message delivery for daemon messages (no job)." - console = all, !skipped, !saved, !audit - append = "@logdir@/bareos.log" = all, !skipped, !audit - append = "@logdir@/bareos-audit.log" = audit -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/messages/Standard.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/messages/Standard.conf.in deleted file mode 100644 index b3556ba8c23..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/messages/Standard.conf.in +++ /dev/null @@ -1,7 +0,0 @@ -Messages { - Name = Standard - Description = "Reasonable message delivery -- send most everything to email address and to the console." - console = all, !skipped, !saved, !audit - append = "@logdir@/bareos.log" = all, !skipped, !saved, !audit - catalog = all, !skipped, !saved, !audit -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Differential.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Differential.conf deleted file mode 100644 index 25ce24821ab..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Differential.conf +++ /dev/null @@ -1,10 +0,0 @@ -Pool { - Name = Differential - Pool Type = Backup - Recycle = yes # Bareos can automatically recycle Volumes - AutoPrune = yes # Prune expired volumes - Volume Retention = 90 days # How long should the Differential Backups be kept? (#09) - Maximum Volume Bytes = 10G # Limit Volume size to something reasonable - Maximum Volumes = 100 # Limit number of Volumes in Pool - Label Format = "Differential-" # Volumes will be labeled "Differential-" -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Full.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Full.conf deleted file mode 100644 index 867fc66b483..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Full.conf +++ /dev/null @@ -1,10 +0,0 @@ -Pool { - Name = Full - Pool Type = Backup - Recycle = yes # Bareos can automatically recycle Volumes - AutoPrune = yes # Prune expired volumes - Volume Retention = 365 days # How long should the Full Backups be kept? (#06) - Maximum Volume Bytes = 50G # Limit Volume size to something reasonable - Maximum Volumes = 100 # Limit number of Volumes in Pool - Label Format = "Full-" # Volumes will be labeled "Full-" -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Incremental.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Incremental.conf deleted file mode 100644 index f4dbbab6650..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Incremental.conf +++ /dev/null @@ -1,10 +0,0 @@ -Pool { - Name = Incremental - Pool Type = Backup - Recycle = yes # Bareos can automatically recycle Volumes - AutoPrune = yes # Prune expired volumes - Volume Retention = 30 days # How long should the Incremental Backups be kept? (#12) - Maximum Volume Bytes = 1G # Limit Volume size to something reasonable - Maximum Volumes = 100 # Limit number of Volumes in Pool - Label Format = "Incremental-" # Volumes will be labeled "Incremental-" -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Scratch.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Scratch.conf deleted file mode 100644 index 3a489b19871..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/pool/Scratch.conf +++ /dev/null @@ -1,4 +0,0 @@ -Pool { - Name = Scratch - Pool Type = Scratch -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/profile/operator.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/profile/operator.conf deleted file mode 100644 index 94747ffb39b..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/profile/operator.conf +++ /dev/null @@ -1,18 +0,0 @@ -Profile { - Name = operator - Description = "Profile allowing normal Bareos operations." - - Command ACL = !.bvfs_clear_cache, !.exit, !.sql - Command ACL = !configure, !create, !delete, !purge, !prune, !sqlquery, !umount, !unmount - Command ACL = *all* - - Catalog ACL = *all* - Client ACL = *all* - FileSet ACL = *all* - Job ACL = *all* - Plugin Options ACL = *all* - Pool ACL = *all* - Schedule ACL = *all* - Storage ACL = *all* - Where ACL = "|!(){}" # invalid chars -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/storage/File.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/storage/File.conf.in deleted file mode 100644 index a2622f719e2..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-dir.d/storage/File.conf.in +++ /dev/null @@ -1,8 +0,0 @@ -Storage { - Name = File - Address = @hostname@ - Password = "@sd_password@" - Device = FileStorage - Media Type = File - SD Port = @sd_port@ -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/client/myself.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/client/myself.conf.in deleted file mode 100644 index 98a085900fe..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/client/myself.conf.in +++ /dev/null @@ -1,15 +0,0 @@ -Client { - Name = @basename@-fd - Maximum Concurrent Jobs = 20 - - # remove comment from "Plugin Directory" to load plugins from specified directory. - # if "Plugin Names" is defined, only the specified plugins will be loaded, - # otherwise all filedaemon plugins (*-fd.so) from the "Plugin Directory". - # - # Plugin Directory = "@python_plugin_module_src_fd@" - # Plugin Names = "" - - Working Directory = "@working_dir@" - FD Port = @fd_port@ - -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in deleted file mode 100644 index c8dc7085a45..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/director/bareos-dir.conf.in +++ /dev/null @@ -1,5 +0,0 @@ -Director { - Name = bareos-dir - Password = "@fd_password@" - Description = "Allow the configured Director to access this file daemon." -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/messages/Standard.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/messages/Standard.conf deleted file mode 100644 index 97788e00573..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-fd.d/messages/Standard.conf +++ /dev/null @@ -1,5 +0,0 @@ -Messages { - Name = Standard - Director = bareos-dir = all, !skipped, !restored - Description = "Send relevant messages to the Director." -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/device/FileStorage.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/device/FileStorage.conf deleted file mode 100644 index 11a639bc688..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/device/FileStorage.conf +++ /dev/null @@ -1,11 +0,0 @@ -Device { - Name = FileStorage - Media Type = File - Archive Device = storage - LabelMedia = yes; # lets Bareos label unlabeled media - Random Access = yes; - AutomaticMount = yes; # when device opened, read it - RemovableMedia = no; - AlwaysOpen = no; - Description = "File device. A connecting Director must have the same Name and MediaType." -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in deleted file mode 100644 index deef3360c2d..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/director/bareos-dir.conf.in +++ /dev/null @@ -1,5 +0,0 @@ -Director { - Name = bareos-dir - Password = "@sd_password@" - Description = "Director, who is permitted to contact this storage daemon." -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/messages/Standard.conf b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/messages/Standard.conf deleted file mode 100644 index 468348e62fc..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/messages/Standard.conf +++ /dev/null @@ -1,5 +0,0 @@ -Messages { - Name = Standard - Director = bareos-dir = all - Description = "Send all messages to the Director." -} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in b/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in deleted file mode 100644 index 14e2fd4eb1d..00000000000 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bareos-sd.d/storage/bareos-sd.conf.in +++ /dev/null @@ -1,14 +0,0 @@ -Storage { - Name = bareos-sd - Maximum Concurrent Jobs = 20 - - # remove comment from "Plugin Directory" to load plugins from specified directory. - # if "Plugin Names" is defined, only the specified plugins will be loaded, - # otherwise all storage plugins (*-sd.so) from the "Plugin Directory". - # - # Plugin Directory = "@python_plugin_module_src_sd@" - # Plugin Names = "" - Working Directory = "@working_dir@" - SD Port = @sd_port@ - @sd_backend_config@ -} diff --git a/systemtests/tests/config-syntax-crash/testrunner b/systemtests/tests/config-syntax-crash/testrunner deleted file mode 100755 index 53cf586a657..00000000000 --- a/systemtests/tests/config-syntax-crash/testrunner +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -set -e -set -o pipefail -set -u -# -# Start and stop the daemons to make sure our configuration does not crash them. -# This checks -# - an invalid entry in an ACL -# -TestName="$(basename "$(pwd)")" -export TestName - -JobName=backup-bareos-fd - -#shellcheck source=../environment.in -. ./environment - -#shellcheck source=../scripts/functions -. "${rscripts}"/functions -"${rscripts}"/cleanup -"${rscripts}"/setup - -start_test - -start_bareos -stop_bareos - -end_test diff --git a/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/client-bareos-fd.conf b/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/client-bareos-fd.conf index c0d05f910a6..e1213f86f1f 100644 --- a/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/client-bareos-fd.conf +++ b/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/client-bareos-fd.conf @@ -1,20 +1,9 @@ Console { - Name = client-bareos-fd - Password = secret + Name = "client-bareos-fd" + Password = "secret" TLS Enable = no - # Command ACL from operator profile. - Command ACL = !.bvfs_clear_cache, !.exit, !.sql - Command ACL = !configure, !create, !delete, !purge, !prune, !sqlquery, !umount, !unmount - Command ACL = *all* + Profile = "operator" - Catalog ACL = *all* - Client ACL = "bareos-fd" - FileSet ACL = *all* - Job ACL = *all* - Plugin Options ACL = *all* - Pool ACL = *all* - Schedule ACL = *all* - Storage ACL = *all* - Where ACL = *all* + Client ACL = "bareos-fd", "!*all*" } diff --git a/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/limited-operator.conf b/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/limited-operator.conf new file mode 100644 index 00000000000..2143eb176fb --- /dev/null +++ b/systemtests/tests/python-bareos/etc/bareos/bareos-dir.d/console/limited-operator.conf @@ -0,0 +1,10 @@ +Console { + Name = "limited-operator" + Password = "secret" + TLS Enable = no + + Profile = "operator" + + Command ACL = "!.consoles" + Where ACL = "/.*/tmp/bareos-restores-limited-operator", "!*all*" +} diff --git a/systemtests/tests/config-syntax-crash/etc/bareos/bconsole.conf.in b/systemtests/tests/python-bareos/etc/bareos/bconsole-limited-operator.conf.in similarity index 63% rename from systemtests/tests/config-syntax-crash/etc/bareos/bconsole.conf.in rename to systemtests/tests/python-bareos/etc/bareos/bconsole-limited-operator.conf.in index 50b647c1d71..49d97bfede5 100644 --- a/systemtests/tests/config-syntax-crash/etc/bareos/bconsole.conf.in +++ b/systemtests/tests/python-bareos/etc/bareos/bconsole-limited-operator.conf.in @@ -6,5 +6,10 @@ Director { Name = @basename@-dir DIRport = @dir_port@ Address = @hostname@ - Password = "@dir_password@" + Password = "INVALID" +} + +Console { + name = limited-operator + password = secret } diff --git a/systemtests/tests/python-bareos/list_unittests.py b/systemtests/tests/python-bareos/list_unittests.py index 9f28223a408..23368eb3bcd 100755 --- a/systemtests/tests/python-bareos/list_unittests.py +++ b/systemtests/tests/python-bareos/list_unittests.py @@ -3,7 +3,7 @@ # # BAREOSĀ® - Backup Archiving REcovery Open Sourced # -# Copyright (C) 2021-2022 Bareos GmbH & Co. KG +# Copyright (C) 2021-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 @@ -20,22 +20,15 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. - -from __future__ import print_function import unittest -def list_tests_from(path, **kwargs): - loader = unittest.TestLoader() - suite = loader.discover(path, **kwargs) - for atest in suite: - tests = atest._tests - for atest in tests: - for btest in atest._tests: - btestname = btest.__str__().split() - print(btestname[1][1:-1] + "." + btestname[0]) +def print_suite(suite): + if hasattr(suite, "__iter__"): + for x in suite: + print_suite(x) + else: + print(suite) -if __name__ == "__main__": - # list_tests_from(".", pattern="*test*.py") - list_tests_from(".") +print_suite(unittest.defaultTestLoader.discover(".")) diff --git a/systemtests/tests/python-bareos/test_acl.py b/systemtests/tests/python-bareos/test_acl.py index f1e5f3c396c..891eb1ff376 100644 --- a/systemtests/tests/python-bareos/test_acl.py +++ b/systemtests/tests/python-bareos/test_acl.py @@ -1,7 +1,7 @@ # # BAREOS - Backup Archiving REcovery Open Sourced # -# Copyright (C) 2019-2023 Bareos GmbH & Co. KG +# Copyright (C) 2019-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 @@ -20,7 +20,6 @@ # -*- coding: utf-8 -*- -from __future__ import print_function import json import logging import os @@ -136,7 +135,7 @@ def test_restore_with_client_acl(self): logger.debug(str(result)) # TODO: This is a bug. # ACL checking does not work here, - # because this jobid should be accessable in this console. + # because this jobid should be accessible in this console. # self.assertIn(b'No Job found for JobId', result) result = console_bareos_fd.call("find *") logger.debug(str(result)) @@ -160,7 +159,7 @@ def test_restore_with_client_acl(self): # # 5: Select the most recent backup for a client # - # Only the bareos-fd client should be accessable + # Only the bareos-fd client should be accessible # and is therefore autoselected. # result = console_bareos_fd.call("5") @@ -300,12 +299,8 @@ def test_json_list_media_with_pool_acl(self): ), ) - # TODO: - # IMHO this is a bug. # This console should not see volumes in the Full pool. - # It needs to be fixed in the server side code. - with self.assertRaises(AssertionError): - self._test_no_volume_in_pool(console_overwrite, console_password, "Full") + self._test_no_volume_in_pool(console_overwrite, console_password, "Full") def test_json_list_jobid_with_job_acl(self): """ @@ -402,3 +397,100 @@ def test_status_subscription_admin(self): def test_status_subscription_user_fails(self): with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): self._test_status_subscription("client-bareos-fd", "secret") + + def test_limited_command_acl(self): + """ + The console "limited-operator" uses the profile "operator", + but disallows the command ".consoles". + """ + logger = logging.getLogger() + + username = "limited-operator" + password = "secret" + + console = bareos.bsock.DirectorConsoleJson( + address=self.director_address, + port=self.director_port, + name=username, + password=password, + **self.director_extra_options + ) + + # verify that the command ".consoles" is not allowed. + # We expect that the Bareos Director will return something like: + # { + # "jsonrpc": "2.0", + # "id": null, + # "error": { + # "code": 1, + # "message": "failed", + # "data": { + # "result": {}, + # "messages": { + # "error": [ + # ".consoles: is an invalid command.\n" + # ] + # } + # } + # } + # } + # DirectorConsoleJson will raise an exception, if the result contains the "error" key. + with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): + result = console.call(".consoles") + + # verify that substings of the ".consoles" command are not allowed. + with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): + result = console.call(".consol") + + # verify that other commands are allowed. + result = console.call(".jobs") + self.assertGreaterEqual(len(result["jobs"]), 1) + + def test_limiting_where_acl(self): + """ + Try different where options on restore. + """ + logger = logging.getLogger() + + username = "limited-operator" + password = "secret" + + jobname = "backup-bareos-fd" + client = "bareos-fd" + # console WhereACL is expected to be: + # WhereAcl = , "!*all*" + allowed_restore_path = "{}/tmp/bareos-restores-{}".format(os.getcwd(), username) + + console = bareos.bsock.DirectorConsoleJson( + address=self.director_address, + port=self.director_port, + name=username, + password=password, + **self.director_extra_options + ) + + # retrieve or create a jobid of a valid backup job + backup_jobid = self.get_backup_jobid(console, jobname) + + # restore with default where path + restore_jobid = self.run_restore(console, client=client, jobid=backup_jobid) + + # restore with allowed where path + restore_jobid = self.run_restore( + console, + client=client, + jobid=backup_jobid, + extra="where={}".format(allowed_restore_path), + ) + + # try to restore with non-allowed where path + with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): + restore_jobid = self.run_restore( + console, client=client, jobid=backup_jobid, extra="where=/tmp/INVALID" + ) + + # try to restore with non-allowed empty where path + with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): + restore_jobid = self.run_restore( + console, client=client, jobid=backup_jobid, extra="where=" + ) diff --git a/systemtests/tests/python-bareos/test_json_config.py b/systemtests/tests/python-bareos/test_json_config.py index f6bede7dedb..5f76ebe55a7 100644 --- a/systemtests/tests/python-bareos/test_json_config.py +++ b/systemtests/tests/python-bareos/test_json_config.py @@ -1,7 +1,7 @@ # # BAREOS - Backup Archiving REcovery Open Sourced # -# Copyright (C) 2019-2021 Bareos GmbH & Co. KG +# Copyright (C) 2019-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 @@ -73,7 +73,7 @@ def test_show_command(self): self.assertFalse( self.check_resource(director, resourcesname, newclient), - u"Resource {} in {} already exists.".format(newclient, resourcesname), + "Resource {} in {} already exists.".format(newclient, resourcesname), ) with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): @@ -83,7 +83,7 @@ def test_show_command(self): director, resourcesname, newclient, - u"client={} password={} address=127.0.0.1".format(newclient, newpassword), + "client={} password={} address=127.0.0.1".format(newclient, newpassword), ) director.call("show all") @@ -122,14 +122,14 @@ def test_configure_add_with_quotes(self): self.assertFalse( self.check_resource(director, resourcesname, testname), - u"Resource {} in {} already exists.".format(testname, resourcesname), + "Resource {} in {} already exists.".format(testname, resourcesname), ) self.configure_add( director, resourcesname, testname, - u'job jobdefs=DefaultJob name={} description="{}" runafterjob="{}" priority={}'.format( + 'job jobdefs=DefaultJob name={} description="{}" runafterjob="{}" priority={}'.format( testname, stringwithwhitespace, testscript, priority ), ) @@ -150,3 +150,48 @@ def test_configure_add_with_quotes(self): result["jobs"][testname]["priority"], priority, ) + + def _test_configure_add_conole_with_where_acl(self, name, where_acl): + username = self.get_operator_username() + password = self.get_operator_password(username) + + director = bareos.bsock.DirectorConsoleJson( + address=self.director_address, + port=self.director_port, + name=username, + password=password, + **self.director_extra_options + ) + + resourcetype = "console" + + try: + os.remove("etc/bareos/bareos-dir.d/{}/{}.conf".format(resourcetype, name)) + director.call("reload") + except OSError: + pass + + self.assertFalse( + self.check_resource(director, resourcetype, name), + "Resource {} in {} already exists.".format(name, resourcetype), + ) + + self.configure_add( + director, + resourcetype, + name, + '{} name={} password=secret profile=operator WhereACL="{}"'.format( + resourcetype, name, where_acl + ), + ) + + self.assertTrue(self.check_resource(director, resourcetype, name)) + + def test_configure_add_valid_where_acl(self): + self._test_configure_add_conole_with_where_acl( + "valid-WhereAcl", "/tmp/validpath" + ) + + def test_configure_add_invalid_where_acl(self): + with self.assertRaises(bareos.exceptions.JsonRpcErrorReceivedException): + self._test_configure_add_conole_with_where_acl("invalid-WhereAcl", "|!(){}")