From a614be4c4958ea81eeb7fed08818392f00a58662 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 28 May 2025 09:53:10 +0200 Subject: [PATCH 1/2] Added policy function to get ACLs Ticket: CFE-4529 Changelog: Title Signed-off-by: Lars Erik Wik --- libpromises/acl_tools.h | 13 +++++++++ libpromises/acl_tools_posix.c | 31 ++++++++++++++++++++++ libpromises/evalfunction.c | 50 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/libpromises/acl_tools.h b/libpromises/acl_tools.h index 80d1176db4..cd9ec4a073 100644 --- a/libpromises/acl_tools.h +++ b/libpromises/acl_tools.h @@ -25,6 +25,19 @@ #ifndef CFENGINE_ACL_TOOLS_H #define CFENGINE_ACL_TOOLS_H +#include +#include + + +/** + * @brief Get ACLs from a file or directory + * @param Path to file or directory + * @param access Get access ACLs if true, otherwise default ACLs + * @return List of ACLs. On error, NULL is returned and errno is set to + * indicate the error. + */ +Rlist *GetACLs(const char *path, bool access); + bool CopyACLs(const char *src, const char *dst, bool *change); /** diff --git a/libpromises/acl_tools_posix.c b/libpromises/acl_tools_posix.c index 5fc2304763..8b21c70e24 100644 --- a/libpromises/acl_tools_posix.c +++ b/libpromises/acl_tools_posix.c @@ -259,8 +259,39 @@ bool AllowAccessForUsers(const char *path, StringSet *users, bool allow_writes, return true; } +Rlist *GetACLs(const char *path, bool access) +{ + assert(path != NULL); + + acl_t acl = acl_get_file(path, access ? ACL_TYPE_ACCESS : ACL_TYPE_DEFAULT); + if (acl == NULL) + { + return NULL; + } + + char *text = acl_to_any_text(acl, NULL, ',', 0); + if (text == NULL) + { + acl_free(acl); + return NULL; + } + + Rlist *lst = RlistFromSplitString(text, ','); + + acl_free(text); + acl_free(acl); + return lst; +} + #elif !defined(__MINGW32__) /* !HAVE_LIBACL */ +Rlist *GetACLs(ARG_UNUSED const char *path, ARG_UNUSED bool access) +{ + /* TODO: Handle Windows ACLs (see ENT-13019) */ + errno = ENOTSUP; + return NULL; +} + bool CopyACLs(ARG_UNUSED const char *src, ARG_UNUSED const char *dst, bool *change) { if (change != NULL) diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 498476d5af..dd0721620d 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -82,6 +82,10 @@ #include #include +#include +#include +#include +#include #ifdef HAVE_LIBCURL #include @@ -654,6 +658,43 @@ static Rlist *GetHostsFromLastseenDB(Seq *host_data, time_t horizon, HostsSeenFi /*********************************************************************/ +static FnCallResult FnCallGetACLs(ARG_UNUSED EvalContext *ctx, + ARG_UNUSED const Policy *policy, + const FnCall *fp, + const Rlist *final_args) +{ + assert(fp != NULL); + assert(final_args != NULL); + assert(final_args->next != NULL); + + const char *path = RlistScalarValue(final_args); + const char *type = RlistScalarValue(final_args->next); + assert(StringEqual(type, "default") || StringEqual(type, "access")); + +#ifdef _WIN32 + /* TODO: Policy function to read Windows ACLs (ENT-13019) */ + Rlist *acls = NULL; + errno = ENOTSUP; +#else + Rlist *acls = GetACLs(path, StringEqual(type, "access")); +#endif /* _WIN32 */ + if (acls == NULL) + { + Log((errno != ENOTSUP) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Function %s failed to get ACLs for '%s': %s", + fp->name, path, GetErrorStr()); + + if (errno != ENOTSUP) + { + return FnFailure(); + } /* else we'll just return an empty list instead */ + } + + return (FnCallResult) { FNCALL_SUCCESS, { acls, RVAL_TYPE_LIST } }; +} + +/*********************************************************************/ + static FnCallResult FnCallAnd(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, @@ -9776,6 +9817,13 @@ static const FnCallArg AND_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; +static const FnCallArg GET_ACLS_ARGS[] = +{ + {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Path to file or directory"}, + {"default,access", CF_DATA_TYPE_OPTION, "Whether to get default or access ACL"}, + {NULL, CF_DATA_TYPE_NONE, NULL}, +}; + static const FnCallArg AGO_ARGS[] = { {"0,1000", CF_DATA_TYPE_INT, "Years"}, @@ -10820,6 +10868,8 @@ const FnCallType CF_FNCALL_TYPES[] = FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), FnCallTypeNew("accumulated", CF_DATA_TYPE_INT, ACCUM_ARGS, &FnCallAccumulatedDate, "Convert an accumulated amount of time into a system representation", FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), + FnCallTypeNew("getacls", CF_DATA_TYPE_STRING_LIST, GET_ACLS_ARGS, &FnCallGetACLs, "Get ACLs of a given file", + FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL), FnCallTypeNew("ago", CF_DATA_TYPE_INT, AGO_ARGS, &FnCallAgoDate, "Convert a time relative to now to an integer system representation", FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL), FnCallTypeNew("and", CF_DATA_TYPE_CONTEXT, AND_ARGS, &FnCallAnd, "Calculate whether all arguments evaluate to true", From 99a7fb20043dd53084b9677b1d78856a974b1aad Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 2 Jun 2025 16:38:19 +0200 Subject: [PATCH 2/2] Added acceptance test for getacls() Ticket: CFE-4529 Signed-off-by: Lars Erik Wik Co-authored-by: Ole Herman Schumacher Elgesem <4048546+olehermanse@users.noreply.github.com> --- libpromises/evalfunction.c | 3 +- tests/acceptance/10_files/12_acl/getacls.cf | 90 +++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/10_files/12_acl/getacls.cf diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index dd0721620d..44774e8a4c 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -658,7 +658,8 @@ static Rlist *GetHostsFromLastseenDB(Seq *host_data, time_t horizon, HostsSeenFi /*********************************************************************/ -static FnCallResult FnCallGetACLs(ARG_UNUSED EvalContext *ctx, +static FnCallResult FnCallGetACLs( + ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *final_args) diff --git a/tests/acceptance/10_files/12_acl/getacls.cf b/tests/acceptance/10_files/12_acl/getacls.cf new file mode 100644 index 0000000000..619792c8f8 --- /dev/null +++ b/tests/acceptance/10_files/12_acl/getacls.cf @@ -0,0 +1,90 @@ +############################################################################## +# +# Test policy function getacls() +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +############################################################################## + +body acl user_root_rwx_acl +{ + acl_method => "append"; + acl_default => "access"; + aces => { "user:root:rwx" }; +} + +############################################################################## + +bundle agent init +{ + files: + "$(G.testdir)/." + create => "true", + acl => user_root_rwx_acl, + handle => "Default ACLs is set"; + "$(G.testdir)/foo" + create => "true", + depends_on => { "Default ACLs is set" }, + comment => "Inherits ACLs from parent directory"; +} + +############################################################################## + +bundle agent test +{ + meta: + "description" -> { "CFE-4529" } + string => "Test policy function getacls()"; + + "test_soft_fail" + string => "windows", + meta => { "ENT-13019" }; + + vars: + "default_acls" + slist => getacls("$(G.testdir)", "default"), + if => fileexists("$(G.testdir)"); + "access_acls" + slist => getacls("$(G.testdir)/foo", "access"), + if => fileexists("$(G.testdir)/foo"); +} + +############################################################################## + +bundle agent check +{ + classes: + "acls_not_supported" + expression => eval("$(with) == 0", "class", "infix"), + with => length("test.default_acls"), + comment => "getacls() returns empty list if unsupported"; + "default_ok" + expression => some("$(expected)", "test.default_acls"); + "access_ok" + expression => some("$(expected)", "test.access_acls"); + + vars: + "expected" + string => ".*user:root:rwx.*"; + + reports: + acls_not_supported:: + "$(this.promise_filename) Skip/unsupported"; + default_ok&access_ok:: + "$(this.promise_filename) Pass"; + !(default_ok&access_ok):: + "$(this.promise_filename) FAIL"; + "Expecting one match of '$(expected)' in default ACLs [$(with)]" + with => join(", ", "test.default_acls"); + "Expecting one match of '$(expected)' in access ACLs [$(with)]" + with => join(", ", "test.access_acls"); +} + +##############################################################################