Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions libpromises/acl_tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@
#ifndef CFENGINE_ACL_TOOLS_H
#define CFENGINE_ACL_TOOLS_H

#include <stdbool.h>
#include <rlist.h>


/**
* @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);

/**
Expand Down
31 changes: 31 additions & 0 deletions libpromises/acl_tools_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
51 changes: 51 additions & 0 deletions libpromises/evalfunction.c
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@
#include <libgen.h>

#include <ctype.h>
#include <cf3.defs.h>
#include <compiler.h>
#include <rlist.h>
#include <acl_tools.h>

#ifdef HAVE_LIBCURL
#include <curl/curl.h>
Expand Down Expand Up @@ -654,6 +658,44 @@ 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,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@larsewi I am a bit unsure about this error. Directories might not have acls on them, and rather than error, it sort of feels more correct to simply return an empty list. I want to say that getindcies and some other functions behave similarly to that.

This came up after the workshop when i was following up on a note to add example output to the documentation example.

root@hub:~# cf-agent -Kf ./t.cf 
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
R: Access acl: user::rw-
R: Access acl: group::r--
R: Access acl: other::r--
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
   error: Function getacls failed to get ACLs for '/etc': No data available
R: Default acl: $(default_acls)
root@hub:~# cat t.cf 
bundle agent __main__
{

  vars:

    "default_acls"
      slist => getacls("/etc", "default");

    "access_acls"
      slist => getacls("/tmp/bar", "access");

  reports:
    "Default acl: $(default_acls)";
    "Access acl: $(access_acls)";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was triggered by this update from @nickanderson and error is definitely not okl I assume we want the same as the getfacl command:

11:25 install3:~ $ getfacl /etc
getfacl: Removing leading '/' from absolute path names
# file: etc
# owner: root
# group: root
user::rwx
group::r-x
other::r-x

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 🚀 Fix here #5902

"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,
Expand Down Expand Up @@ -9776,6 +9818,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"},
Expand Down Expand Up @@ -10820,6 +10869,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",
Expand Down
90 changes: 90 additions & 0 deletions tests/acceptance/10_files/12_acl/getacls.cf
Original file line number Diff line number Diff line change
@@ -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");
}

##############################################################################
Loading