From d7e8ff3930eaccd5c058235524a5752eefa785c9 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 28 Mar 2025 07:50:11 +0100 Subject: [PATCH 01/38] Added utility functions to override immutable bit Ticket: ENT-10961 Signed-off-by: Lars Erik Wik --- libpromises/Makefile.am | 1 + libpromises/override_fsattrs.c | 140 +++++++++++++++++++++++++++++++++ libpromises/override_fsattrs.h | 58 ++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 libpromises/override_fsattrs.c create mode 100644 libpromises/override_fsattrs.h diff --git a/libpromises/Makefile.am b/libpromises/Makefile.am index b2123440de..88387e12bb 100644 --- a/libpromises/Makefile.am +++ b/libpromises/Makefile.am @@ -137,6 +137,7 @@ libpromises_la_SOURCES = \ modes.c \ monitoring_read.c monitoring_read.h \ ornaments.c ornaments.h \ + override_fsattrs.c override_fsattrs.h \ policy.c policy.h \ parser.c parser.h \ parser_helpers.h \ diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c new file mode 100644 index 0000000000..0b0fed4d00 --- /dev/null +++ b/libpromises/override_fsattrs.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +bool OverrideImmutableBegin(const char *orig, char *copy, size_t copy_len) +{ + srand(time(NULL)); /* Seed random number generator */ + int rand_number = rand() % 999999; + assert(rand_number >= 0); + + /* Inspired by mkstemp(3) */ + int ret = snprintf(copy, copy_len, "%s.%06d" CF_NEW, orig, rand_number); + if (ret < 0 || (size_t) ret >= copy_len) + { + Log(LOG_LEVEL_ERR, + "Failed to generate name for temporary copy of '%s': Filename is too long (%d >= %zu)", + orig, + ret, + copy_len); + return false; + } + + /* We'll match the original file permissions on commit */ + if (!CopyRegularFileDiskPerms(orig, copy, 0600)) + { + Log(LOG_LEVEL_ERR, + "Failed to copy file '%s' to temporary file '%s'", + orig, + copy); + return false; + } + + return true; +} + +bool OverrideImmutableCommit(const char *orig, const char *copy) +{ + struct stat sb; + if (lstat(orig, &sb) == -1) + { + Log(LOG_LEVEL_ERR, "Failed to stat file '%s'", orig); + unlink(copy); + return false; + } + + if (chmod(copy, sb.st_mode) == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to change mode bits on file '%s' to %04jo: %s", + orig, + (uintmax_t) sb.st_mode, + GetErrorStr()); + unlink(copy); + return false; + } + + /* If the operations on the file system attributes fails for any reason, + * we can still proceed to try to replace the original file. We will only + * log an actual error in case of an unexpected failure (i.e., when + * FS_ATTRS_FAILURE is returned). Other failures will be logged as verbose + * messages because they can be useful, but are be quite verbose. */ + + bool is_immutable; + FSAttrsResult res = FSAttrsGetImmutableFlag(orig, &is_immutable); + if (res == FS_ATTRS_SUCCESS) + { + if (is_immutable) + { + res = FSAttrsUpdateImmutableFlag(orig, false); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Temporarily cleared immutable bit for file '%s'", + orig); + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR + : LOG_LEVEL_VERBOSE, + "Failed to temporarily clear immutable bit for file '%s': %s", + orig, + FSAttrsErrorCodeToString(res)); + } + } + else + { + Log(LOG_LEVEL_DEBUG, + "The immutable bit is not set on file '%s'", + orig); + } + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Failed to get immutable bit from file '%s': %s", + orig, + FSAttrsErrorCodeToString(res)); + } + + if (rename(copy, orig) == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to replace original file '%s' with copy '%s'", + orig, + copy); + unlink(copy); + return false; + } + + if ((res == FS_ATTRS_SUCCESS) && is_immutable) + { + res = FSAttrsUpdateImmutableFlag(orig, true); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Reset immutable bit after temporarily clearing it from file '%s'", + orig); + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Failed to reset immutable bit after temporarily clearing it from file '%s': %s", + orig, + FSAttrsErrorCodeToString(res)); + } + } + + return true; +} + +bool OverrideImmutableAbort(ARG_UNUSED const char *orig, const char *copy) +{ + assert(copy != NULL); + return unlink(copy) == 0; +} diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h new file mode 100644 index 0000000000..1e19303603 --- /dev/null +++ b/libpromises/override_fsattrs.h @@ -0,0 +1,58 @@ +/* + Copyright 2025 Northern.tech AS + + This file is part of CFEngine 3 - written and maintained by Northern.tech AS. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; version 3. + + 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + To the extent this program is licensed as part of the Enterprise + versions of CFEngine, the applicable Commercial Open Source License + (COSL) may apply to this file if you as a licensee so wish it. See + included file COSL.txt. +*/ + +#ifndef CFENGINE_OVERRIDE_FSATTRS_H +#define CFENGINE_OVERRIDE_FSATTRS_H + +#include +#include + +/** + * @brief Creates a mutable copy of the original file + * @param orig The original file (may be immutable) + * @param copy Updated to contain the filename of the mutable copy + * @param copy_len The size of the buffer to store the filename of the copy + * @return false in case of failure + */ +bool OverrideImmutableBegin(const char *orig, char *copy, size_t copy_len); + +/** + * @brief Temporarily clears the immutable bit of the original file and + * replaces it with the mutated copy + * @param orig The original file (may be immutable) + * @param copy The mutated copy to replace the original + * @return false in case of failure + * @note The immutable bit is reset to it's original state + */ +bool OverrideImmutableCommit(const char *orig, const char *copy); + +/** + * @brief Simply unlinks the mutable copy + * @param orig Not used (reserved in for future use) + * @param copy The mutated copy to unlink + * @return false in case of failure (but you probably don't care) + */ +bool OverrideImmutableAbort(const char *orig, const char *copy); + +#endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From 4ebc7b2d92b9d6aca0b983e610f2506e4ab2f8d2 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 28 Jan 2025 15:15:00 +0100 Subject: [PATCH 02/38] Added body syntax for controlling file system attributes Added syntax for `body fsattrs` with a boolean constraint `immutable`. It currently does nothing, but this will change in following commits. Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/attributes.c | 19 +++++++++++++++++++ libpromises/attributes.h | 1 + libpromises/cf3.defs.h | 10 ++++++++++ libpromises/mod_files.c | 10 ++++++++++ 4 files changed, 40 insertions(+) diff --git a/libpromises/attributes.c b/libpromises/attributes.c index 3f5f1380a2..b9fe8674ba 100644 --- a/libpromises/attributes.c +++ b/libpromises/attributes.c @@ -55,6 +55,7 @@ Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp) attr.haveselect = PromiseGetConstraintAsBoolean(ctx, "file_select", pp); attr.haverename = PromiseGetConstraintAsBoolean(ctx, "rename", pp); attr.havedelete = PromiseGetConstraintAsBoolean(ctx, "delete", pp); + attr.havefsattrs = PromiseBundleOrBodyConstraintExists(ctx, "fsattrs", pp); attr.content = PromiseGetConstraintAsRval(pp, "content", RVAL_TYPE_SCALAR); attr.haveperms = PromiseGetConstraintAsBoolean(ctx, "perms", pp); attr.havechange = PromiseGetConstraintAsBoolean(ctx, "changes", pp); @@ -89,6 +90,7 @@ Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp) attr.perms = GetPermissionConstraints(ctx, pp); attr.select = GetSelectConstraints(ctx, pp); attr.delete = GetDeleteConstraints(ctx, pp); + attr.fsattrs = GetFSAttrsConstraints(ctx, pp); attr.rename = GetRenameConstraints(ctx, pp); attr.change = GetChangeMgtConstraints(ctx, pp); attr.copy = GetCopyConstraints(ctx, pp); @@ -830,6 +832,23 @@ FileDelete GetDeleteConstraints(const EvalContext *ctx, const Promise *pp) return f; } +/*******************************************************************/ + +FileFSAttrs GetFSAttrsConstraints(const EvalContext *ctx, const Promise *pp) +{ + assert(ctx != NULL); + assert(pp != NULL); + + FileFSAttrs f = + { + .immutable = PromiseGetConstraintAsBoolean(ctx, "immutable", pp), + .haveimmutable = PromiseBundleOrBodyConstraintExists(ctx, "immutable", pp), + }; + + return f; +} + + /*******************************************************************/ FileRename GetRenameConstraints(const EvalContext *ctx, const Promise *pp) diff --git a/libpromises/attributes.h b/libpromises/attributes.h index f7089c1688..02237f08b1 100644 --- a/libpromises/attributes.h +++ b/libpromises/attributes.h @@ -68,6 +68,7 @@ ENTERPRISE_FUNC_0ARG_DECLARE(HashMethod, GetBestFileChangeHashMethod); FileChange GetChangeMgtConstraints(const EvalContext *ctx, const Promise *pp); FileCopy GetCopyConstraints(const EvalContext *ctx, const Promise *pp); FileDelete GetDeleteConstraints(const EvalContext *ctx, const Promise *pp); +FileFSAttrs GetFSAttrsConstraints(const EvalContext *ctx, const Promise *pp); FileLink GetLinkConstraints(const EvalContext *ctx, const Promise *pp); FileRename GetRenameConstraints(const EvalContext *ctx, const Promise *pp); FileSelect GetSelectConstraints(const EvalContext *ctx, const Promise *pp); diff --git a/libpromises/cf3.defs.h b/libpromises/cf3.defs.h index 039afee5c8..05d311308b 100644 --- a/libpromises/cf3.defs.h +++ b/libpromises/cf3.defs.h @@ -1061,6 +1061,14 @@ typedef struct /*************************************************************************/ +typedef struct +{ + int immutable; + int haveimmutable; +} FileFSAttrs; + +/*************************************************************************/ + typedef struct { char *newname; @@ -1530,6 +1538,7 @@ typedef struct FilePerms perms; FileCopy copy; FileDelete delete; + FileFSAttrs fsattrs; char *content; FileRename rename; FileChange change; @@ -1581,6 +1590,7 @@ typedef struct int haveselect; int haverename; int havedelete; + int havefsattrs; int haveperms; int havechange; int havecopy; diff --git a/libpromises/mod_files.c b/libpromises/mod_files.c index 7f2abf904a..5ee17053ee 100644 --- a/libpromises/mod_files.c +++ b/libpromises/mod_files.c @@ -227,6 +227,15 @@ static const ConstraintSyntax delete_constraints[] = static const BodySyntax delete_body = BodySyntaxNew("delete", delete_constraints, NULL, SYNTAX_STATUS_NORMAL); +static const ConstraintSyntax fsattrs_constraints[] = +{ + CONSTRAINT_SYNTAX_GLOBAL, + ConstraintSyntaxNewBool("immutable", "true to set / false to clear the immutable flag", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewNull() +}; + +static const BodySyntax fsattrs_body = BodySyntaxNew("fsattrs", fsattrs_constraints, NULL, SYNTAX_STATUS_NORMAL); + static const ConstraintSyntax rename_constraints[] = { CONSTRAINT_SYNTAX_GLOBAL, @@ -339,6 +348,7 @@ static const ConstraintSyntax CF_FILES_BODIES[] = ConstraintSyntaxNewBody("copy_from", ©_from_body, "Criteria for copying file from a source", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewBool("create", "true/false whether to create non-existing file. Default value: false", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewBody("delete", &delete_body, "Criteria for deleting files", SYNTAX_STATUS_NORMAL), + ConstraintSyntaxNewBody("fsattrs", &fsattrs_body, "Control file system attributes", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewString("content", CF_ANYSTRING, "Complete content the promised file should contain", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewBody("depth_search", &depth_search_body, "Criteria for file depth searches", SYNTAX_STATUS_NORMAL), ConstraintSyntaxNewBody("edit_defaults", &edit_defaults_body, "Default promise details for file edits", SYNTAX_STATUS_NORMAL), From 5243590fd36f1576c9a05db8a2dba7bc067cd48c Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 6 Feb 2025 16:08:33 +0100 Subject: [PATCH 03/38] Files promise can now modify immutable bit in file system attributes Ticket: ENT-10961, CFE-1840 Changelog: Title Signed-off-by: Lars Erik Wik --- cf-agent/verify_files.c | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/cf-agent/verify_files.c b/cf-agent/verify_files.c index 83b9db8c73..556aa75d02 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -60,6 +60,7 @@ #include #include /* PrepareChangesChroot(), RecordFileChangedInChroot() */ #include +#include static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp); static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp); @@ -345,6 +346,62 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi changes_path = chrooted_path; } + bool is_immutable = false; /* We assume not in case of failure */ + FSAttrsResult res = FSAttrsGetImmutableFlag(changes_path, &is_immutable); + if (res != FS_ATTRS_SUCCESS) + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Failed to get the state of the immutable bit from file '%s': %s", + changes_path, FSAttrsErrorCodeToString(res)); + } + + if (a.havefsattrs && a.fsattrs.haveimmutable && !a.fsattrs.immutable) + { + /* Here we only handle the clearing of the immutable the immutable + * bit. Later we'll handle the setting of the immutable bit. */ + if (is_immutable) + { + res = FSAttrsUpdateImmutableFlag(changes_path, false); + switch (res) + { + case FS_ATTRS_SUCCESS: + RecordChange(ctx, pp, &a, + "Cleared the immutable bit on file '%s'", + changes_path); + result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); + break; + case FS_ATTRS_FAILURE: + RecordFailure(ctx, pp, &a, + "Failed to clear the immutable bit on file '%s'", + changes_path); + result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + break; + case FS_ATTRS_NOT_SUPPORTED: + /* We will not treat this as a promise failure because this + * will happen on many platforms and filesystems. Instead we + * will log a verbose message to make it apparent for the + * users. */ + Log(LOG_LEVEL_VERBOSE, + "Failed to clear the immutable bit on file '%s': %s", + changes_path, FSAttrsErrorCodeToString(res)); + break; + case FS_ATTRS_DOES_NOT_EXIST: + /* File does not exist. Nothing to do really, but let's log a + * debug message for good measures */ + Log(LOG_LEVEL_DEBUG, + "Failed to clear the immutable bit on file '%s': %s", + changes_path, FSAttrsErrorCodeToString(res)); + break; + } + } + else + { + RecordNoChange(ctx, pp, &a, + "The immutable bit is not set on file '%s' as promised", + changes_path); + } + } + if (lstat(changes_path, &oslb) == -1) /* Careful if the object is a link */ { if ((a.create) || (a.touch)) @@ -586,6 +643,51 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi } } + if (a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable) + { + /* Here we only handle the setting of the immutable bit. Previously we + * handled the clearing of the immutable bit. */ + if (is_immutable) + { + RecordNoChange(ctx, pp, &a, + "The immutable bit is already set on file '%s' as promised", + changes_path); + } + else + { + res = FSAttrsUpdateImmutableFlag(changes_path, true); + switch (res) + { + case FS_ATTRS_SUCCESS: + Log(LOG_LEVEL_VERBOSE, "Set the immutable bit on file '%s'", + changes_path); + break; + case FS_ATTRS_FAILURE: + /* Things still may be fine as long as the agent does not try to mutate the file */ + Log(LOG_LEVEL_VERBOSE, + "Failed to set the immutable bit on file '%s': %s", + changes_path, FSAttrsErrorCodeToString(res)); + break; + case FS_ATTRS_NOT_SUPPORTED: + /* We will not treat this as a promise failure because this + * will happen on many platforms and filesystems. Instead we + * will log a verbose message to make it apparent for the + * users. */ + Log(LOG_LEVEL_VERBOSE, + "Failed to set the immutable bit on file '%s': %s", + changes_path, FSAttrsErrorCodeToString(res)); + break; + case FS_ATTRS_DOES_NOT_EXIST: + /* File does not exist. Nothing to do really, but let's log a + * debug message for good measures */ + Log(LOG_LEVEL_DEBUG, + "Failed to set the immutable bit on file '%s': %s", + changes_path, FSAttrsErrorCodeToString(res)); + break; + } + } + } + // Once more in case a file has been created as a result of editing or copying exists = (lstat(changes_path, &osb) != -1); From 1e7420461ffb6a07ca4b050d5085dbf6b84c2bcc Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Apr 2025 17:36:46 +0200 Subject: [PATCH 04/38] Added argument to NOOP OverrideImmutable functions Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 31 ++++++++++++++++++++++++++++--- libpromises/override_fsattrs.h | 12 +++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 0b0fed4d00..06f589acd5 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -7,8 +7,23 @@ #include #include -bool OverrideImmutableBegin(const char *orig, char *copy, size_t copy_len) +bool OverrideImmutableBegin( + const char *orig, char *copy, size_t copy_len, bool override) { + if (!override) + { + size_t ret = strlcpy(copy, orig, copy_len); + if (ret >= copy_len) + { + Log(LOG_LEVEL_ERR, + "Failed to copy filename '%s': Filename too long (%zu >= %zu)", + ret, + copy_len); + return false; + } + return true; + } + srand(time(NULL)); /* Seed random number generator */ int rand_number = rand() % 999999; assert(rand_number >= 0); @@ -38,8 +53,13 @@ bool OverrideImmutableBegin(const char *orig, char *copy, size_t copy_len) return true; } -bool OverrideImmutableCommit(const char *orig, const char *copy) +bool OverrideImmutableCommit(const char *orig, const char *copy, bool override) { + if (!override) + { + return true; + } + struct stat sb; if (lstat(orig, &sb) == -1) { @@ -133,8 +153,13 @@ bool OverrideImmutableCommit(const char *orig, const char *copy) return true; } -bool OverrideImmutableAbort(ARG_UNUSED const char *orig, const char *copy) +bool OverrideImmutableAbort( + ARG_UNUSED const char *orig, const char *copy, bool override) { assert(copy != NULL); + if (!override) + { + return true; + } return unlink(copy) == 0; } diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index 1e19303603..142b3ba8fc 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -33,26 +33,32 @@ * @param orig The original file (may be immutable) * @param copy Updated to contain the filename of the mutable copy * @param copy_len The size of the buffer to store the filename of the copy + * @param override Whether to actually do override (original filename is + * copied to copy buffer if false) * @return false in case of failure */ -bool OverrideImmutableBegin(const char *orig, char *copy, size_t copy_len); +bool OverrideImmutableBegin( + const char *orig, char *copy, size_t copy_len, bool override); /** * @brief Temporarily clears the immutable bit of the original file and * replaces it with the mutated copy * @param orig The original file (may be immutable) * @param copy The mutated copy to replace the original + * @param override Whether to actually do override * @return false in case of failure * @note The immutable bit is reset to it's original state */ -bool OverrideImmutableCommit(const char *orig, const char *copy); +bool OverrideImmutableCommit( + const char *orig, const char *copy, bool override); /** * @brief Simply unlinks the mutable copy * @param orig Not used (reserved in for future use) * @param copy The mutated copy to unlink + * @param override NOOP if override is false * @return false in case of failure (but you probably don't care) */ -bool OverrideImmutableAbort(const char *orig, const char *copy); +bool OverrideImmutableAbort(const char *orig, const char *copy, bool override); #endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From c07955fa95900e3050acd04fb161cbfa66eeb2cf Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Apr 2025 17:53:13 +0200 Subject: [PATCH 05/38] Combined OverrideImmutable[Commit|Abort] functions Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 20 ++++++++------------ libpromises/override_fsattrs.h | 12 ++---------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 06f589acd5..275e648633 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -17,6 +17,7 @@ bool OverrideImmutableBegin( { Log(LOG_LEVEL_ERR, "Failed to copy filename '%s': Filename too long (%zu >= %zu)", + orig, ret, copy_len); return false; @@ -53,13 +54,19 @@ bool OverrideImmutableBegin( return true; } -bool OverrideImmutableCommit(const char *orig, const char *copy, bool override) +bool OverrideImmutableCommit( + const char *orig, const char *copy, bool override, bool abort) { if (!override) { return true; } + if (abort) + { + return unlink(copy) == 0; + } + struct stat sb; if (lstat(orig, &sb) == -1) { @@ -152,14 +159,3 @@ bool OverrideImmutableCommit(const char *orig, const char *copy, bool override) return true; } - -bool OverrideImmutableAbort( - ARG_UNUSED const char *orig, const char *copy, bool override) -{ - assert(copy != NULL); - if (!override) - { - return true; - } - return unlink(copy) == 0; -} diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index 142b3ba8fc..90e22a2e18 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -46,19 +46,11 @@ bool OverrideImmutableBegin( * @param orig The original file (may be immutable) * @param copy The mutated copy to replace the original * @param override Whether to actually do override + * @param abort Whether to abort the override * @return false in case of failure * @note The immutable bit is reset to it's original state */ bool OverrideImmutableCommit( - const char *orig, const char *copy, bool override); - -/** - * @brief Simply unlinks the mutable copy - * @param orig Not used (reserved in for future use) - * @param copy The mutated copy to unlink - * @param override NOOP if override is false - * @return false in case of failure (but you probably don't care) - */ -bool OverrideImmutableAbort(const char *orig, const char *copy, bool override); + const char *orig, const char *copy, bool override, bool abort); #endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From d5ada416d2eb252d9e67d7c55cef0a4e5eb85e41 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Apr 2025 17:55:22 +0200 Subject: [PATCH 06/38] Content attribute can now override immutable bit The content attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/cf-agent/verify_files.c b/cf-agent/verify_files.c index 556aa75d02..7d04785b9a 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -61,11 +61,12 @@ #include /* PrepareChangesChroot(), RecordFileChangedInChroot() */ #include #include +#include static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp); static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp); static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, const Attributes *attr, - const Promise *pp); + const Promise *pp, bool override_immutable); /*****************************************************************************/ @@ -402,6 +403,11 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi } } + /* If we encounter any promises to mutate the file and the immutable + * attribute in body fsattrs is "true", we will override the immutable bit + * by temporarily clearing it when ever needed. */ + const bool override_immutable = a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable; + if (lstat(changes_path, &oslb) == -1) /* Careful if the object is a link */ { if ((a.create) || (a.touch)) @@ -611,7 +617,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi Log(LOG_LEVEL_VERBOSE, "Replacing '%s' with content '%s'", path, a.content); - PromiseResult render_result = WriteContentFromString(ctx, path, &a, pp); + PromiseResult render_result = WriteContentFromString(ctx, path, &a, pp, override_immutable); result = PromiseResultUpdate(result, render_result); goto exit; @@ -760,7 +766,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi /*****************************************************************************/ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, const Attributes *attr, - const Promise *pp) + const Promise *pp, bool override_immutable) { assert(path != NULL); assert(attr != NULL); @@ -795,20 +801,28 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, return result; } - FILE *f = safe_fopen(changes_path, "w"); + char override_path[PATH_MAX]; + if (!OverrideImmutableBegin(changes_path, override_path, sizeof(override_path), override_immutable)) + { + RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path); + return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + } + + FILE *f = safe_fopen(override_path, "w"); if (f == NULL) { RecordFailure(ctx, pp, attr, "Cannot open file '%s' for writing", path); + OverrideImmutableCommit(changes_path, override_path, override_immutable, true); return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } + bool override_abort = false; Writer *w = FileWriter(f); if (WriterWriteLen(w, attr->content, bytes_to_write) == bytes_to_write ) { RecordChange(ctx, pp, attr, "Updated file '%s' with content '%s'", path, attr->content); - result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); } else @@ -816,9 +830,16 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, RecordFailure(ctx, pp, attr, "Failed to update file '%s' with content '%s'", path, attr->content); + override_abort = true; result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } WriterClose(w); + + if (!OverrideImmutableCommit(changes_path, override_path, override_immutable, override_abort)) + { + RecordFailure(ctx, pp, attr, "Failed to override immutable bit on file '%s'", changes_path); + result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); + } } return result; From c9ba31d0bebfb52e587453baf97f7a72eb09d69a Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 3 Apr 2025 16:25:03 +0200 Subject: [PATCH 07/38] Store override_immutable variable in EvalContext This way we don't have to explicitly pass it to a gazillion functions. Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- cf-agent/verify_files.c | 12 ++++++++---- libpromises/eval_context.c | 13 +++++++++++++ libpromises/eval_context.h | 3 +++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cf-agent/verify_files.c b/cf-agent/verify_files.c index 7d04785b9a..9b034f0b93 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -66,7 +66,7 @@ static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp); static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp); static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, const Attributes *attr, - const Promise *pp, bool override_immutable); + const Promise *pp); /*****************************************************************************/ @@ -406,7 +406,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi /* If we encounter any promises to mutate the file and the immutable * attribute in body fsattrs is "true", we will override the immutable bit * by temporarily clearing it when ever needed. */ - const bool override_immutable = a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable; + EvalContextOverrideImmutableSet(ctx, a.havefsattrs && a.fsattrs.haveimmutable && a.fsattrs.immutable && is_immutable); if (lstat(changes_path, &oslb) == -1) /* Careful if the object is a link */ { @@ -617,7 +617,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi Log(LOG_LEVEL_VERBOSE, "Replacing '%s' with content '%s'", path, a.content); - PromiseResult render_result = WriteContentFromString(ctx, path, &a, pp, override_immutable); + PromiseResult render_result = WriteContentFromString(ctx, path, &a, pp); result = PromiseResultUpdate(result, render_result); goto exit; @@ -711,6 +711,9 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi } exit: + /* Reset this to false before next file promise */ + EvalContextOverrideImmutableSet(ctx, false); + free(chrooted_path); if (AttrHasNoAction(&a)) { @@ -766,7 +769,7 @@ static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promi /*****************************************************************************/ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, const Attributes *attr, - const Promise *pp, bool override_immutable) + const Promise *pp) { assert(path != NULL); assert(attr != NULL); @@ -794,6 +797,7 @@ static PromiseResult WriteContentFromString(EvalContext *ctx, const char *path, if (!HashesMatch(existing_content_digest, promised_content_digest, CF_DEFAULT_DIGEST)) { + bool override_immutable = EvalContextOverrideImmutableGet(ctx); if (!MakingChanges(ctx, pp, attr, &result, "update file '%s' with content '%s'", path, attr->content)) diff --git a/libpromises/eval_context.c b/libpromises/eval_context.c index 50a433dc11..13b9685723 100644 --- a/libpromises/eval_context.c +++ b/libpromises/eval_context.c @@ -192,8 +192,21 @@ struct EvalContext_ RemoteVarPromisesMap *remote_var_promises; bool dump_reports; + bool override_immutable; }; +void EvalContextOverrideImmutableSet(EvalContext *ctx, bool should_override) +{ + assert(ctx != NULL); + ctx->override_immutable = should_override; +} + +bool EvalContextOverrideImmutableGet(EvalContext *ctx) +{ + assert(ctx != NULL); + return ctx->override_immutable; +} + void EvalContextSetConfig(EvalContext *ctx, const GenericAgentConfig *config) { assert(ctx != NULL); diff --git a/libpromises/eval_context.h b/libpromises/eval_context.h index 6ad09c08b3..48fefd76c2 100644 --- a/libpromises/eval_context.h +++ b/libpromises/eval_context.h @@ -128,6 +128,9 @@ void EvalContextHeapPersistentSave(EvalContext *ctx, const char *name, unsigned void EvalContextHeapPersistentRemove(const char *context); void EvalContextHeapPersistentLoadAll(EvalContext *ctx); +void EvalContextOverrideImmutableSet(EvalContext *ctx, bool should_override); +bool EvalContextOverrideImmutableGet(EvalContext *ctx); + /** * Sets negated classes (persistent classes that should not be defined). * From 0c533193ebe1c3bd108fdb332cf729926d8fe5ee Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 3 Apr 2025 17:09:02 +0200 Subject: [PATCH 08/38] Added function to override immutable bit and rename file Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 98 ++++++++++++++++++++-------------- libpromises/override_fsattrs.h | 11 ++++ 2 files changed, 69 insertions(+), 40 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 275e648633..723b790e66 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -86,74 +86,92 @@ bool OverrideImmutableCommit( return false; } + return OverrideImmutableRename(copy, orig, override); +} + +bool OverrideImmutableRename( + const char *old_filename, const char *new_filename, bool override) +{ + assert(old_filename != NULL); + assert(new_filename != NULL); + /* If the operations on the file system attributes fails for any reason, * we can still proceed to try to replace the original file. We will only * log an actual error in case of an unexpected failure (i.e., when * FS_ATTRS_FAILURE is returned). Other failures will be logged as verbose * messages because they can be useful, but are be quite verbose. */ + FSAttrsResult res; bool is_immutable; - FSAttrsResult res = FSAttrsGetImmutableFlag(orig, &is_immutable); - if (res == FS_ATTRS_SUCCESS) + + if (override) { - if (is_immutable) + res = FSAttrsGetImmutableFlag(new_filename, &is_immutable); + if (res == FS_ATTRS_SUCCESS) { - res = FSAttrsUpdateImmutableFlag(orig, false); - if (res == FS_ATTRS_SUCCESS) + if (is_immutable) { - Log(LOG_LEVEL_VERBOSE, - "Temporarily cleared immutable bit for file '%s'", - orig); + res = FSAttrsUpdateImmutableFlag(new_filename, false); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Temporarily cleared immutable bit for file '%s'", + new_filename); + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR + : LOG_LEVEL_VERBOSE, + "Failed to temporarily clear immutable bit for file '%s': %s", + new_filename, + FSAttrsErrorCodeToString(res)); + } } else { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR - : LOG_LEVEL_VERBOSE, - "Failed to temporarily clear immutable bit for file '%s': %s", - orig, - FSAttrsErrorCodeToString(res)); + Log(LOG_LEVEL_DEBUG, + "The immutable bit is not set on file '%s'", + new_filename); } } else { - Log(LOG_LEVEL_DEBUG, - "The immutable bit is not set on file '%s'", - orig); + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Failed to get immutable bit from file '%s': %s", + new_filename, + FSAttrsErrorCodeToString(res)); } } - else - { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, - "Failed to get immutable bit from file '%s': %s", - orig, - FSAttrsErrorCodeToString(res)); - } - if (rename(copy, orig) == -1) + if (rename(old_filename, new_filename) == -1) { Log(LOG_LEVEL_ERR, "Failed to replace original file '%s' with copy '%s'", - orig, - copy); - unlink(copy); + new_filename, + old_filename); + unlink(old_filename); return false; } - if ((res == FS_ATTRS_SUCCESS) && is_immutable) + if (override) { - res = FSAttrsUpdateImmutableFlag(orig, true); - if (res == FS_ATTRS_SUCCESS) - { - Log(LOG_LEVEL_VERBOSE, - "Reset immutable bit after temporarily clearing it from file '%s'", - orig); - } - else + if ((res == FS_ATTRS_SUCCESS) && is_immutable) { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, - "Failed to reset immutable bit after temporarily clearing it from file '%s': %s", - orig, - FSAttrsErrorCodeToString(res)); + res = FSAttrsUpdateImmutableFlag(new_filename, true); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Reset immutable bit after temporarily clearing it from file '%s'", + new_filename); + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR + : LOG_LEVEL_VERBOSE, + "Failed to reset immutable bit after temporarily clearing it from file '%s': %s", + new_filename, + FSAttrsErrorCodeToString(res)); + } } } diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index 90e22a2e18..ba24e8ae54 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -53,4 +53,15 @@ bool OverrideImmutableBegin( bool OverrideImmutableCommit( const char *orig, const char *copy, bool override, bool abort); +/** + * @brief Temporarily clears the immutable bit of the old file and renames the + * new to the old + * @param old_filename Filename of the old file + * @param new_filename Filename of the new file + * @param override Whether to actually do override + * @return false in case of failure + */ +bool OverrideImmutableRename( + const char *old_filename, const char *new_filename, bool override); + #endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From 83e0ed8d21dc7ad7b4cfa9d0d8317d6da7dc1a8c Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 3 Apr 2025 17:21:17 +0200 Subject: [PATCH 09/38] copy_from attribute can now override immutable bit The copy_from attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files_utils.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 541c68bd89..43ecb9d976 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -67,6 +67,7 @@ #include #include /* PrepareChangesChroot(), RecordFileChangedInChroot() */ #include /* GetGroupName(), GetUserName() */ +#include #include @@ -1925,7 +1926,8 @@ bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, con else #endif { - if (rename(changes_new, changes_dest) == 0) + bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (OverrideImmutableRename(changes_new, changes_dest, override_immutable)) { RecordChange(ctx, pp, attr, "Moved '%s' to '%s'", new, dest); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); @@ -1937,7 +1939,7 @@ bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, con dest, GetErrorStr()); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); - if (backupok && (rename(changes_backup, changes_dest) == 0)) + if (backupok && OverrideImmutableRename(changes_backup, changes_dest, override_immutable)) { RecordChange(ctx, pp, attr, "Restored '%s' from '%s'", dest, backup); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); From b69401b75d61dff52dfc842cb2a6743759a384cb Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 3 Apr 2025 18:34:29 +0200 Subject: [PATCH 10/38] Added function to override immutable bit and delete file Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 47 ++++++++++++++++++++++++++++++++++ libpromises/override_fsattrs.h | 8 ++++++ 2 files changed, 55 insertions(+) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 723b790e66..eba3aeff8e 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -177,3 +177,50 @@ bool OverrideImmutableRename( return true; } + +bool OverrideImmutableDelete(const char *filename, bool override) +{ + assert(filename != NULL); + + bool is_immutable = false; + if (override) + { + FSAttrsResult res = FSAttrsGetImmutableFlag(filename, &is_immutable); + if (res == FS_ATTRS_SUCCESS) + { + if (is_immutable) + { + res = FSAttrsUpdateImmutableFlag(filename, false); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Cleared immutable bit for file '%s'", + filename); + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR + : LOG_LEVEL_VERBOSE, + "Failed to clear immutable bit for file '%s': %s", + filename, + FSAttrsErrorCodeToString(res)); + } + } + else + { + Log(LOG_LEVEL_DEBUG, + "The immutable bit is not set on file '%s'", + filename); + } + } + else + { + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Failed to get immutable bit from file '%s': %s", + filename, + FSAttrsErrorCodeToString(res)); + } + } + + return unlink(filename) == 0; +} diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index ba24e8ae54..21373700a4 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -64,4 +64,12 @@ bool OverrideImmutableCommit( bool OverrideImmutableRename( const char *old_filename, const char *new_filename, bool override); +/** + * @brief Delete immutable file + * @param filename Name of the file to delete + * @param override Whether to actually do override + * @return false in case of failure + */ +bool OverrideImmutableDelete(const char *filename, bool override); + #endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From f8db2b0c3784cf1cabddb0456a42eaa52ece1479 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 3 Apr 2025 18:40:03 +0200 Subject: [PATCH 11/38] delete attribute can now override immutable bit The delete attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files_utils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 43ecb9d976..2446fd4244 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -2419,8 +2419,8 @@ static PromiseResult VerifyDelete(EvalContext *ctx, { if (!S_ISDIR(sb->st_mode)) /* file,symlink */ { - int ret = unlink(lastnode); - if (ret == -1) + bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (!OverrideImmutableDelete(lastnode, override_immutable)) { RecordFailure(ctx, pp, attr, "Couldn't unlink '%s' tidying. (unlink: %s)", path, GetErrorStr()); From 1e6b32651919a14f6de6cfa0e7da14887e20ab29 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 4 Apr 2025 11:29:03 +0200 Subject: [PATCH 12/38] edit_line and edit_xml attributes can now override immutable bit The edit_line and edit_xml attributes of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/files_edit.c | 8 ++++---- cf-agent/files_edit.h | 2 +- cf-agent/verify_files.c | 2 +- libpromises/files_operators.c | 10 ++++++---- libpromises/files_operators.h | 4 ++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/cf-agent/files_edit.c b/cf-agent/files_edit.c index 6833efc2d3..a3e27e4eff 100644 --- a/cf-agent/files_edit.c +++ b/cf-agent/files_edit.c @@ -128,7 +128,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c RecordNoChange(ctx, pp, a, "No edit changes to file '%s' need saving", ec->filename); } - else if (SaveItemListAsFile(ec->file_start, ec->changes_filename, a, ec->new_line_mode)) + else if (SaveItemListAsFile(ctx, ec->file_start, ec->changes_filename, a, ec->new_line_mode)) { RecordChange(ctx, pp, a, "Edited file '%s'", ec->filename); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); @@ -151,7 +151,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, c ec->filename); } } - else if (SaveXmlDocAsFile(ec->xmldoc, ec->changes_filename, a, ec->new_line_mode)) + else if (SaveXmlDocAsFile(ctx, ec->xmldoc, ec->changes_filename, a, ec->new_line_mode)) { RecordChange(ctx, pp, a, "Edited xml file '%s'", ec->filename); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); @@ -254,8 +254,8 @@ static bool SaveXmlCallback(const char *dest_filename, void *param, /*********************************************************************/ -bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode) +bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode) { - return SaveAsFile(&SaveXmlCallback, doc, file, a, new_line_mode); + return SaveAsFile(ctx, &SaveXmlCallback, doc, file, a, new_line_mode); } #endif /* HAVE_LIBXML2 */ diff --git a/cf-agent/files_edit.h b/cf-agent/files_edit.h index 6bb8551b3e..9c19c39786 100644 --- a/cf-agent/files_edit.h +++ b/cf-agent/files_edit.h @@ -61,7 +61,7 @@ void FinishEditContext(EvalContext *ctx, EditContext *ec, #ifdef HAVE_LIBXML2 bool LoadFileAsXmlDoc(xmlDocPtr *doc, const char *file, EditDefaults ed, bool only_checks); -bool SaveXmlDocAsFile(xmlDocPtr doc, const char *file, +bool SaveXmlDocAsFile(EvalContext *ctx, xmlDocPtr doc, const char *file, const Attributes *a, NewLineMode new_line_mode); #endif diff --git a/cf-agent/verify_files.c b/cf-agent/verify_files.c index 9b034f0b93..165dccbca2 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -988,7 +988,7 @@ static PromiseResult RenderTemplateMustache(EvalContext *ctx, edcontext->filename, message); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } - else if (SaveAsFile(SaveBufferCallback, output_buffer, + else if (SaveAsFile(ctx, SaveBufferCallback, output_buffer, edcontext->changes_filename, attr, edcontext->new_line_mode)) { diff --git a/libpromises/files_operators.c b/libpromises/files_operators.c index f8d212edb3..d450b8a7e5 100644 --- a/libpromises/files_operators.c +++ b/libpromises/files_operators.c @@ -47,6 +47,7 @@ #include #include #include +#include bool MoveObstruction(EvalContext *ctx, char *from, const Attributes *attr, const Promise *pp, PromiseResult *result) @@ -149,7 +150,7 @@ bool MoveObstruction(EvalContext *ctx, char *from, const Attributes *attr, const /*********************************************************************/ -bool SaveAsFile(SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode) +bool SaveAsFile(EvalContext *ctx, SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode) { assert(a != NULL); struct stat statbuf; @@ -277,7 +278,8 @@ bool SaveAsFile(SaveCallbackFn callback, void *param, const char *file, const At unlink(backup); } - if (rename(new, BufferData(deref_file)) == -1) + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (!OverrideImmutableRename(new, BufferData(deref_file), override_immutable)) { Log(LOG_LEVEL_ERR, "Can't rename '%s' to %s - so promised edits could not be moved into place. (rename: %s)", new, BufferData(pretty_file), GetErrorStr()); @@ -331,10 +333,10 @@ static bool SaveItemListCallback(const char *dest_filename, void *param, NewLine /*********************************************************************/ -bool SaveItemListAsFile(Item *liststart, const char *file, const Attributes *a, NewLineMode new_line_mode) +bool SaveItemListAsFile(EvalContext *ctx, Item *liststart, const char *file, const Attributes *a, NewLineMode new_line_mode) { assert(a != NULL); - return SaveAsFile(&SaveItemListCallback, liststart, file, a, new_line_mode); + return SaveAsFile(ctx, &SaveItemListCallback, liststart, file, a, new_line_mode); } // Some complex logic here to enable warnings of diffs to be given diff --git a/libpromises/files_operators.h b/libpromises/files_operators.h index 9ef4816a68..6d531c4d3a 100644 --- a/libpromises/files_operators.h +++ b/libpromises/files_operators.h @@ -31,8 +31,8 @@ bool MoveObstruction(EvalContext *ctx, char *from, const Attributes *attr, const Promise *pp, PromiseResult *result); typedef bool (*SaveCallbackFn)(const char *dest_filename, void *param, NewLineMode new_line_mode); -bool SaveAsFile(SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode); -bool SaveItemListAsFile(Item *liststart, const char *file, const Attributes *a, NewLineMode new_line_mode); +bool SaveAsFile(EvalContext *ctx, SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode); +bool SaveItemListAsFile(EvalContext *ctx, Item *liststart, const char *file, const Attributes *a, NewLineMode new_line_mode); bool CompareToFile(EvalContext *ctx, const Item *liststart, const char *file, const Attributes *a, const Promise *pp, PromiseResult *result); From d1f222a8df2cb5847cf724a9f00c45338e0cf600 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 22 May 2025 13:50:55 +0200 Subject: [PATCH 13/38] override_fsattrs.c: refactored code Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 170 ++++++++++++++------------------- 1 file changed, 74 insertions(+), 96 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index eba3aeff8e..4c99c585e1 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -89,59 +89,91 @@ bool OverrideImmutableCommit( return OverrideImmutableRename(copy, orig, override); } -bool OverrideImmutableRename( - const char *old_filename, const char *new_filename, bool override) +static void TemporarilyClearImmutableBit( + const char *filename, + bool override, + FSAttrsResult *res, + bool *is_immutable) { - assert(old_filename != NULL); - assert(new_filename != NULL); - - /* If the operations on the file system attributes fails for any reason, - * we can still proceed to try to replace the original file. We will only - * log an actual error in case of an unexpected failure (i.e., when - * FS_ATTRS_FAILURE is returned). Other failures will be logged as verbose - * messages because they can be useful, but are be quite verbose. */ - - FSAttrsResult res; - bool is_immutable; + if (!override) + { + return; + } - if (override) + *res = FSAttrsGetImmutableFlag(filename, is_immutable); + if (*res == FS_ATTRS_SUCCESS) { - res = FSAttrsGetImmutableFlag(new_filename, &is_immutable); - if (res == FS_ATTRS_SUCCESS) + if (*is_immutable) { - if (is_immutable) + *res = FSAttrsUpdateImmutableFlag(filename, false); + if (*res == FS_ATTRS_SUCCESS) { - res = FSAttrsUpdateImmutableFlag(new_filename, false); - if (res == FS_ATTRS_SUCCESS) - { - Log(LOG_LEVEL_VERBOSE, - "Temporarily cleared immutable bit for file '%s'", - new_filename); - } - else - { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR - : LOG_LEVEL_VERBOSE, - "Failed to temporarily clear immutable bit for file '%s': %s", - new_filename, - FSAttrsErrorCodeToString(res)); - } + Log(LOG_LEVEL_VERBOSE, + "Temporarily cleared immutable bit for file '%s'", + filename); } else { - Log(LOG_LEVEL_DEBUG, - "The immutable bit is not set on file '%s'", - new_filename); + Log((*res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR + : LOG_LEVEL_VERBOSE, + "Failed to temporarily clear immutable bit for file '%s': %s", + filename, + FSAttrsErrorCodeToString(*res)); } } else + { + Log(LOG_LEVEL_DEBUG, + "The immutable bit is not set on file '%s'", + filename); + } + } + else + { + Log((*res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + "Failed to get immutable bit from file '%s': %s", + filename, + FSAttrsErrorCodeToString(*res)); + } +} + +static void ResetTemporarilyClearedImmutableBit( + const char *filename, bool override, FSAttrsResult res, bool is_immutable) +{ + if (!override) + { + return; + } + + if ((res == FS_ATTRS_SUCCESS) && is_immutable) + { + res = FSAttrsUpdateImmutableFlag(filename, true); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Reset immutable bit after temporarily clearing it from file '%s'", + filename); + } + else { Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, - "Failed to get immutable bit from file '%s': %s", - new_filename, + "Failed to reset immutable bit after temporarily clearing it from file '%s': %s", + filename, FSAttrsErrorCodeToString(res)); } } +} + +bool OverrideImmutableRename( + const char *old_filename, const char *new_filename, bool override) +{ + assert(old_filename != NULL); + assert(new_filename != NULL); + + FSAttrsResult res; + bool is_immutable; + + TemporarilyClearImmutableBit(new_filename, override, &res, &is_immutable); if (rename(old_filename, new_filename) == -1) { @@ -153,27 +185,8 @@ bool OverrideImmutableRename( return false; } - if (override) - { - if ((res == FS_ATTRS_SUCCESS) && is_immutable) - { - res = FSAttrsUpdateImmutableFlag(new_filename, true); - if (res == FS_ATTRS_SUCCESS) - { - Log(LOG_LEVEL_VERBOSE, - "Reset immutable bit after temporarily clearing it from file '%s'", - new_filename); - } - else - { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR - : LOG_LEVEL_VERBOSE, - "Failed to reset immutable bit after temporarily clearing it from file '%s': %s", - new_filename, - FSAttrsErrorCodeToString(res)); - } - } - } + ResetTemporarilyClearedImmutableBit( + new_filename, override, res, is_immutable); return true; } @@ -182,45 +195,10 @@ bool OverrideImmutableDelete(const char *filename, bool override) { assert(filename != NULL); + FSAttrsResult res; bool is_immutable = false; - if (override) - { - FSAttrsResult res = FSAttrsGetImmutableFlag(filename, &is_immutable); - if (res == FS_ATTRS_SUCCESS) - { - if (is_immutable) - { - res = FSAttrsUpdateImmutableFlag(filename, false); - if (res == FS_ATTRS_SUCCESS) - { - Log(LOG_LEVEL_VERBOSE, - "Cleared immutable bit for file '%s'", - filename); - } - else - { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR - : LOG_LEVEL_VERBOSE, - "Failed to clear immutable bit for file '%s': %s", - filename, - FSAttrsErrorCodeToString(res)); - } - } - else - { - Log(LOG_LEVEL_DEBUG, - "The immutable bit is not set on file '%s'", - filename); - } - } - else - { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, - "Failed to get immutable bit from file '%s': %s", - filename, - FSAttrsErrorCodeToString(res)); - } - } + + TemporarilyClearImmutableBit(filename, override, &res, &is_immutable); return unlink(filename) == 0; } From 2cf862f27423514dd92daa61108ad43befd19980 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 22 May 2025 14:22:23 +0200 Subject: [PATCH 14/38] Touch attribute can now override immutable bit The touch attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files_utils.c | 7 ++++--- libpromises/override_fsattrs.c | 23 +++++++++++++++++++++++ libpromises/override_fsattrs.h | 12 ++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 2446fd4244..68043bea10 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -2484,15 +2484,16 @@ static PromiseResult TouchFile(EvalContext *ctx, char *path, const Attributes *a PromiseResult result = PROMISE_RESULT_NOOP; if (MakingChanges(ctx, pp, attr, &result, "update time stamps for '%s'", path)) { - if (utime(ToChangesPath(path), NULL) != -1) + bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (OverrideImmutableUtime(ToChangesPath(path), override_immutable, NULL)) { RecordChange(ctx, pp, attr, "Touched (updated time stamps) for path '%s'", path); result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE); } else { - RecordFailure(ctx, pp, attr, "Touch '%s' failed to update timestamps. (utime: %s)", - path, GetErrorStr()); + RecordFailure(ctx, pp, attr, "Touch '%s' failed to update timestamps", + path); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } } diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 4c99c585e1..6bb578f6a4 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -202,3 +202,26 @@ bool OverrideImmutableDelete(const char *filename, bool override) return unlink(filename) == 0; } + +bool OverrideImmutableUtime( + const char *filename, bool override, const struct utimbuf *times) +{ + assert(filename != NULL); + + FSAttrsResult res; + bool is_immutable; + + TemporarilyClearImmutableBit(filename, override, &res, &is_immutable); + + int ret = utime(filename, times); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to update access and modification times of file '%s'", + filename); + } + + ResetTemporarilyClearedImmutableBit(filename, override, res, is_immutable); + + return ret == 0; +} diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index 21373700a4..60bf460651 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -27,6 +27,7 @@ #include #include +#include /** * @brief Creates a mutable copy of the original file @@ -72,4 +73,15 @@ bool OverrideImmutableRename( */ bool OverrideImmutableDelete(const char *filename, bool override); +/** + * @brief Temporarily clears the immutable bit and changes access and + * modification times of the inode + * @param filename Name of the file to touch + * @param override Whether to actually do override + * @param times Modification times (can be NULL) + * @return false in case of failure + */ +bool OverrideImmutableUtime( + const char *filename, bool override, const struct utimbuf *times); + #endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From 048e9a6e7b1543cf6490efb5ea1353b05ca7b969 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 23 May 2025 13:44:47 +0200 Subject: [PATCH 15/38] Made functions for temporarily clear/reset immutable bit public Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 52 +++++++++++++++------------------- libpromises/override_fsattrs.h | 25 ++++++++++++++++ 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 6bb578f6a4..6b5d0b1fe0 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -89,24 +88,21 @@ bool OverrideImmutableCommit( return OverrideImmutableRename(copy, orig, override); } -static void TemporarilyClearImmutableBit( - const char *filename, - bool override, - FSAttrsResult *res, - bool *is_immutable) +FSAttrsResult TemporarilyClearImmutableBit( + const char *filename, bool override, bool *was_immutable) { if (!override) { - return; + return FS_ATTRS_FAILURE; } - *res = FSAttrsGetImmutableFlag(filename, is_immutable); - if (*res == FS_ATTRS_SUCCESS) + FSAttrsResult res = FSAttrsGetImmutableFlag(filename, was_immutable); + if (res == FS_ATTRS_SUCCESS) { - if (*is_immutable) + if (*was_immutable) { - *res = FSAttrsUpdateImmutableFlag(filename, false); - if (*res == FS_ATTRS_SUCCESS) + res = FSAttrsUpdateImmutableFlag(filename, false); + if (res == FS_ATTRS_SUCCESS) { Log(LOG_LEVEL_VERBOSE, "Temporarily cleared immutable bit for file '%s'", @@ -114,11 +110,11 @@ static void TemporarilyClearImmutableBit( } else { - Log((*res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR - : LOG_LEVEL_VERBOSE, + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR + : LOG_LEVEL_VERBOSE, "Failed to temporarily clear immutable bit for file '%s': %s", filename, - FSAttrsErrorCodeToString(*res)); + FSAttrsErrorCodeToString(res)); } } else @@ -130,22 +126,24 @@ static void TemporarilyClearImmutableBit( } else { - Log((*res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, + Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR : LOG_LEVEL_VERBOSE, "Failed to get immutable bit from file '%s': %s", filename, - FSAttrsErrorCodeToString(*res)); + FSAttrsErrorCodeToString(res)); } + + return res; } -static void ResetTemporarilyClearedImmutableBit( - const char *filename, bool override, FSAttrsResult res, bool is_immutable) +void ResetTemporarilyClearedImmutableBit( + const char *filename, bool override, FSAttrsResult res, bool was_immutable) { if (!override) { return; } - if ((res == FS_ATTRS_SUCCESS) && is_immutable) + if ((res == FS_ATTRS_SUCCESS) && was_immutable) { res = FSAttrsUpdateImmutableFlag(filename, true); if (res == FS_ATTRS_SUCCESS) @@ -170,10 +168,9 @@ bool OverrideImmutableRename( assert(old_filename != NULL); assert(new_filename != NULL); - FSAttrsResult res; bool is_immutable; - - TemporarilyClearImmutableBit(new_filename, override, &res, &is_immutable); + FSAttrsResult res = + TemporarilyClearImmutableBit(new_filename, override, &is_immutable); if (rename(old_filename, new_filename) == -1) { @@ -195,10 +192,8 @@ bool OverrideImmutableDelete(const char *filename, bool override) { assert(filename != NULL); - FSAttrsResult res; bool is_immutable = false; - - TemporarilyClearImmutableBit(filename, override, &res, &is_immutable); + TemporarilyClearImmutableBit(filename, override, &is_immutable); return unlink(filename) == 0; } @@ -208,10 +203,9 @@ bool OverrideImmutableUtime( { assert(filename != NULL); - FSAttrsResult res; bool is_immutable; - - TemporarilyClearImmutableBit(filename, override, &res, &is_immutable); + FSAttrsResult res = + TemporarilyClearImmutableBit(filename, override, &is_immutable); int ret = utime(filename, times); if (ret == -1) diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index 60bf460651..ec1ccf9df1 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -25,6 +25,7 @@ #ifndef CFENGINE_OVERRIDE_FSATTRS_H #define CFENGINE_OVERRIDE_FSATTRS_H +#include #include #include #include @@ -84,4 +85,28 @@ bool OverrideImmutableDelete(const char *filename, bool override); bool OverrideImmutableUtime( const char *filename, bool override, const struct utimbuf *times); +/** + * @brief Temporarily clears the immutable bit (best effort / no guarantees) + * @param filename Name of the file to clear the immutable bit on + * @param override Whether to actually do override + * @param is_immutable Whether or not the file actually was immutable + * @return Result of clearing the immutable bit (no to be interpreted by the + * caller) + */ +FSAttrsResult TemporarilyClearImmutableBit( + const char *filename, bool override, bool *was_immutable); + +/** + * @brief Reset temporarily cleared immutable bit + * @param filename Name of the file to clear the immutable bit on + * @param override Whether to actually do override + * @param res The result from previously clearing it + * @param is_immutable Whether or not the file actually was immutable + */ +void ResetTemporarilyClearedImmutableBit( + const char *filename, + bool override, + FSAttrsResult res, + bool was_immutable); + #endif /* CFENGINE_OVERRIDE_FSATTRS_H */ From d5394a011dc95383cdedecb14d16b200388ab057 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 23 May 2025 13:45:56 +0200 Subject: [PATCH 16/38] Transformer attribute can now override immutable bit The transformer attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files_utils.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 68043bea10..2c8cef1a1c 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -2000,7 +2000,7 @@ static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr, if (!IsExecutable(CommandArg0(BufferData(command)))) { - RecordFailure(ctx, pp, attr, "Transformer '%s' for file '%s' failed", attr->transformer, file); + RecordFailure(ctx, pp, attr, " '%s' for file '%s' failed", attr->transformer, file); *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL); BufferDestroy(command); return false; @@ -2043,6 +2043,10 @@ static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr, } } + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + bool was_immutable = false; + FSAttrsResult res = TemporarilyClearImmutableBit(file, override_immutable, &was_immutable); + Log(LOG_LEVEL_INFO, "Transforming '%s' with '%s'", file, command_str); if ((pop = cf_popen(changes_command, "r", true)) == NULL) { @@ -2086,6 +2090,8 @@ static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr, transRetcode = cf_pclose(pop); + ResetTemporarilyClearedImmutableBit(file, override_immutable, res, was_immutable); + if (VerifyCommandRetcode(ctx, transRetcode, attr, pp, result)) { Log(LOG_LEVEL_INFO, "Transformer '%s' => '%s' seemed to work ok", file, command_str); From 81bc75e569c1714d55bd5ab2666c30b464d68c00 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 26 May 2025 12:03:15 +0200 Subject: [PATCH 17/38] OverrideImmutableRename() new file inherits immutable flag of old When renaming a file using the OverrideImmutableRename() function, the immutable bit is temporarily removed from both the old and new (if it already exists). The immutable bit is then inherited from old to new file. Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 6b5d0b1fe0..a0cc6204dc 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -168,22 +168,26 @@ bool OverrideImmutableRename( assert(old_filename != NULL); assert(new_filename != NULL); - bool is_immutable; - FSAttrsResult res = - TemporarilyClearImmutableBit(new_filename, override, &is_immutable); + bool new_is_immutable; + TemporarilyClearImmutableBit(new_filename, override, &new_is_immutable); + + bool old_is_immutable; + FSAttrsResult res_old = TemporarilyClearImmutableBit( + old_filename, override, &old_is_immutable); if (rename(old_filename, new_filename) == -1) { Log(LOG_LEVEL_ERR, - "Failed to replace original file '%s' with copy '%s'", + "Failed to replace original file '%s' with copy '%s': %s", new_filename, - old_filename); + old_filename, + GetErrorStr()); unlink(old_filename); return false; } ResetTemporarilyClearedImmutableBit( - new_filename, override, res, is_immutable); + new_filename, override, res_old, old_is_immutable); return true; } From 2db3cf9458eeff762b4d5c1c64e6341e6709ba0c Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 26 May 2025 12:00:14 +0200 Subject: [PATCH 18/38] Rename attribute can now override immutable bit The rename attribute of the files promise can now override the immutable bit. The disabled file will inherit the immutable trait of the original file. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files_utils.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 2c8cef1a1c..dee61e43b8 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -2182,10 +2182,10 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat { if (!FileInRepository(newname)) { - if (rename(changes_path, changes_newname) == -1) + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable)) { - RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'. (rename: %s)", - path, GetErrorStr()); + RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else @@ -2335,10 +2335,10 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat if (!FileInRepository(newname)) { - if (rename(changes_path, changes_newname) == -1) + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable)) { - RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'. (rename: %s)", - path, GetErrorStr()); + RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); free(chrooted_path); return result; From ec88ef05efe66d16eec407e244b4f5fd86264692 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 26 May 2025 14:43:46 +0200 Subject: [PATCH 19/38] Perms attribute can now override immutable bit The perms attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/verify_files_utils.c | 7 ++++--- libpromises/override_fsattrs.c | 23 +++++++++++++++++++++++ libpromises/override_fsattrs.h | 11 +++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index dee61e43b8..ac30564485 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -2320,7 +2320,8 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat if (MakingChanges(ctx, pp, attr, &result, "rename file '%s' to '%s'", path, newname)) { - if (safe_chmod(changes_path, newperm) == 0) + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (OverrideImmutableChmod(changes_path, newperm, override_immutable)) { RecordChange(ctx, pp, attr, "Changed permissions of '%s' to 'mode %04jo'", path, (uintmax_t)newperm); @@ -2335,7 +2336,6 @@ static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat if (!FileInRepository(newname)) { - const bool override_immutable = EvalContextOverrideImmutableGet(ctx); if (!OverrideImmutableRename(changes_path, changes_newname, override_immutable)) { RecordFailure(ctx, pp, attr, "Error occurred while renaming '%s'", path); @@ -2653,7 +2653,8 @@ static PromiseResult VerifyFileAttributes(EvalContext *ctx, const char *file, co if (MakingChanges(ctx, pp, attr, &result, "change permissions of '%s' from %04jo to %04jo", file, (uintmax_t)dstat->st_mode & 07777, (uintmax_t)newperm & 07777)) { - if (safe_chmod(changes_file, newperm & 07777) == -1) + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + if (!OverrideImmutableChmod(changes_file, newperm & 07777, override_immutable)) { RecordFailure(ctx, pp, attr, "Failed to change permissions of '%s'. (chmod: %s)", file, GetErrorStr()); diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index a0cc6204dc..4652396fae 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -5,6 +5,7 @@ #include #include #include +#include bool OverrideImmutableBegin( const char *orig, char *copy, size_t copy_len, bool override) @@ -162,6 +163,28 @@ void ResetTemporarilyClearedImmutableBit( } } +bool OverrideImmutableChmod(const char *filename, mode_t mode, bool override) +{ + assert(filename != NULL); + + bool is_immutable; + FSAttrsResult res = + TemporarilyClearImmutableBit(filename, override, &is_immutable); + + int ret = safe_chmod(filename, mode); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to change mode on file '%s': %s", + filename, + GetErrorStr()); + } + + ResetTemporarilyClearedImmutableBit(filename, override, res, is_immutable); + + return ret == 0; +} + bool OverrideImmutableRename( const char *old_filename, const char *new_filename, bool override) { diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index ec1ccf9df1..fc0588bcf3 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -29,6 +29,7 @@ #include #include #include +#include /** * @brief Creates a mutable copy of the original file @@ -55,6 +56,16 @@ bool OverrideImmutableBegin( bool OverrideImmutableCommit( const char *orig, const char *copy, bool override, bool abort); +/** + * @brief Change mode on an immutable file + * @param filename Name of the file + * @param mode The file mode + * @param override Whether to actually do override + * @return false in case of failure + * @note It uses safe_chmod() under the hood + */ +bool OverrideImmutableChmod(const char *filename, mode_t mode, bool override); + /** * @brief Temporarily clears the immutable bit of the old file and renames the * new to the old From 1823cffa31ee9b9663698e28a53d8618d81b25b7 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 26 May 2025 14:58:31 +0200 Subject: [PATCH 20/38] acl attribute can now override immutable bit The acl attribute of the files promise can now override the immutable bit. Ticket: ENT-10961, CFE-1840 Changelog: Commit Signed-off-by: Lars Erik Wik --- cf-agent/acl_posix.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cf-agent/acl_posix.c b/cf-agent/acl_posix.c index e01aff160f..b3d5d88133 100644 --- a/cf-agent/acl_posix.c +++ b/cf-agent/acl_posix.c @@ -33,6 +33,8 @@ #include #include #include /* GetGroupID(), GetUserID() */ +#include +#include "fsattrs.h" #ifdef HAVE_ACL_H # include @@ -389,6 +391,11 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method, acl_free(acl_text_str); return false; } + + bool was_immutable = false; + const bool override_immutable = EvalContextOverrideImmutableGet(ctx); + FSAttrsResult res = TemporarilyClearImmutableBit(changes_path, override_immutable, &was_immutable); + if ((retv = acl_set_file(changes_path, acl_type, acl_new)) != 0) { RecordFailure(ctx, pp, a, @@ -406,6 +413,8 @@ static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method, } acl_free(acl_text_str); + ResetTemporarilyClearedImmutableBit(changes_path, override_immutable, res, was_immutable); + RecordChange(ctx, pp, a, "%s ACL on '%s' successfully changed", acl_type_str, file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); } From dab2d9996a6e013e6f7549ccc209c9a5e4d03cc6 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 28 May 2025 09:54:29 +0200 Subject: [PATCH 21/38] evalfunction.c: Removed trailing whitespace Signed-off-by: Lars Erik Wik --- libpromises/evalfunction.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libpromises/evalfunction.c b/libpromises/evalfunction.c index 498476d5af..d3a78ce8b0 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -946,7 +946,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli assert(fp != NULL); bool allocated = false; JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated); - + // we failed to produce a valid JsonElement, so give up if (json == NULL) { @@ -961,7 +961,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli JsonDestroyMaybe(json, allocated); return FnFailure(); } - + JsonElement *parent = JsonObjectCreate(10); setpwent(); struct passwd *pw; @@ -1006,7 +1006,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli { char uid_string[PRINTSIZE(pw->pw_uid)]; int ret = snprintf(uid_string, sizeof(uid_string), "%u", pw->pw_uid); - + if (ret < 0) { Log(LOG_LEVEL_ERR, "Couldn't convert the uid of '%s' to string in function '%s'", @@ -1038,7 +1038,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli assert((size_t) ret < sizeof(gid_string)); if (!StringMatchFull(value, gid_string)) - { + { can_add_to_json = false; } } @@ -1063,13 +1063,13 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli can_add_to_json = false; } } - else + else { Log(LOG_LEVEL_ERR, "Invalid attribute '%s' in function '%s': not supported", attribute, fp->name); JsonDestroyMaybe(json, allocated); JsonDestroy(parent); - return FnFailure(); + return FnFailure(); } element = JsonIteratorNextValue(&iter); } @@ -1367,7 +1367,7 @@ static FnCallResult FnCallGetGid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const P #endif } -/*********************************************************************/ +/*********************************************************************/ static FnCallResult no_entry(int ret, const FnCall *fp, const char *group_name, bool is_user_db) { @@ -1391,7 +1391,7 @@ static FnCallResult FnCallUserInGroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co assert(finalargs != NULL); #ifdef _WIN32 Log(LOG_LEVEL_ERR, "Function '%s' is POSIX specific", fp->name); - return FnFailure(); + return FnFailure(); #else const char *user_name = RlistScalarValue(finalargs); @@ -1405,7 +1405,7 @@ static FnCallResult FnCallUserInGroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co struct group *grent; char gr_buf[GETGR_R_SIZE_MAX] = {0}; ret = getgrnam_r(group_name, &grp, gr_buf, GETGR_R_SIZE_MAX, &grent); - + if (grent == NULL) { // Group does not exist at all, so cannot be @@ -1432,7 +1432,7 @@ static FnCallResult FnCallUserInGroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED co return no_entry(ret, fp, user_name, true); } return FnReturnContext(grent->gr_gid == pwent->pw_gid); - + #endif } @@ -10800,7 +10800,7 @@ static const FnCallArg IS_DATATYPE_ARGS[] = {NULL, CF_DATA_TYPE_NONE, NULL} }; static const FnCallArg FIND_LOCAL_USERS_ARGS[] = -{ +{ {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter list"}, {NULL, CF_DATA_TYPE_NONE, NULL} }; From 983faa4bb751a6697ea1212ae5f3a27ff8d25eb8 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Wed, 28 May 2025 09:53:10 +0200 Subject: [PATCH 22/38] 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 d3a78ce8b0..b5cc972221 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 4b6c26e3f2a14c826b440a347f33debcf112eacf Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Mon, 2 Jun 2025 16:38:19 +0200 Subject: [PATCH 23/38] Added acceptance test for getacls() Ticket: CFE-4529 Signed-off-by: Lars Erik Wik --- tests/acceptance/10_files/12_acl/getacls.cf | 90 +++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/acceptance/10_files/12_acl/getacls.cf 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"); +} + +############################################################################## From 4053710d6cb105a3a9509de8bba22b5350462902 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:45:45 +0200 Subject: [PATCH 24/38] Added acceptance test to set immutable bit Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/00_immutable.cf | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/00_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/00_immutable.cf b/tests/acceptance/10_files/unsafe/00_immutable.cf new file mode 100644 index 0000000000..54d27cadf4 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/00_immutable.cf @@ -0,0 +1,69 @@ +############################################################################## +# +# Test that agent can set the immutable bit on a file +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/00_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true"; + + commands: + "chattr -i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can set the immutable bit on a file"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable; +} + +bundle agent check +{ + methods: + "check" + usebundle => dcs_passif_output(".*Immutable.*", "", "lsattr -l $(global.testfile)", "$(this.promise_filename)"); +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From c99718d35777bc259ad653f0959c563d73f706a5 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:47:16 +0200 Subject: [PATCH 25/38] Added acceptance test to clear immutable bit Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/01_immutable.cf | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/01_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/01_immutable.cf b/tests/acceptance/10_files/unsafe/01_immutable.cf new file mode 100644 index 0000000000..95e21234fa --- /dev/null +++ b/tests/acceptance/10_files/unsafe/01_immutable.cf @@ -0,0 +1,69 @@ +############################################################################## +# +# Test that agent can clear the immutable bit on a file +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/01_immutable.txt"; +} + +body fsattrs clear_immutable +{ + immutable => "false"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can clear the immutable bit on a file"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => clear_immutable; +} + +bundle agent check +{ + methods: + "check" + usebundle => dcs_passif_output(".*", ".*Immutable.*", "lsattr -l $(global.testfile)", "$(this.promise_filename)"); +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From 5b13fea8bd3aee3134522de07e21e9731a12d6a5 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:48:49 +0200 Subject: [PATCH 26/38] Added acceptance test for immutable with content Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/02_immutable.cf | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/02_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/02_immutable.cf b/tests/acceptance/10_files/unsafe/02_immutable.cf new file mode 100644 index 0000000000..3e201d7856 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/02_immutable.cf @@ -0,0 +1,85 @@ +############################################################################## +# +# Test that agent can override the immutable bit on a file while using the +# content attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/02_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + content => "I'm immutable"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the content attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + content => "But agent can override"; +} + +bundle agent check +{ + vars: + "expected" + string => "But agent can override"; + "actual" + string => readfile("$(global.testfile)"); + + classes: + "ok" + expression => strcmp("$(actual)", "$(expected)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected: '$(expected)', actual: '$(actual)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From 841a31331909e6dcf17d7eac796e74ac7054c3de Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:50:01 +0200 Subject: [PATCH 27/38] Added acceptance test for immutable with copy_from Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/03_immutable.cf | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/03_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/03_immutable.cf b/tests/acceptance/10_files/unsafe/03_immutable.cf new file mode 100644 index 0000000000..e18cdc3191 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/03_immutable.cf @@ -0,0 +1,91 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using copy_from +# attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/03_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + content => "I'm immutable"; + "/tmp/content.txt" + content => "But agent can override"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the copy_from attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + copy_from => local_dcp("/tmp/content.txt"); +} + +bundle agent check +{ + vars: + "expected" + string => "But agent can override"; + "actual" + string => readfile("$(global.testfile)"); + + classes: + "ok" + expression => strcmp("$(actual)", "$(expected)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected: '$(expected)', actual: '$(actual)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; + "$(global.testfile).cfsaved" + delete => tidy; + "/tmp/content.txt" + delete => tidy; +} From f75ea3b98236f84ec23e9536758f87533f01dda6 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:51:19 +0200 Subject: [PATCH 28/38] Added acceptance test for immutable with delete Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/04_immutable.cf | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/04_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/04_immutable.cf b/tests/acceptance/10_files/unsafe/04_immutable.cf new file mode 100644 index 0000000000..00979f7f7b --- /dev/null +++ b/tests/acceptance/10_files/unsafe/04_immutable.cf @@ -0,0 +1,77 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using delete +# attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/04_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + content => "I'm immutable"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the delete attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + delete => tidy; +} + +bundle agent check +{ + classes: + "ok" + expression => not(fileexists("$(global.testfile)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From 8e85661ea82835ac2c9d3741be5e2f612cec1860 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:53:41 +0200 Subject: [PATCH 29/38] Added acceptance test for immutable with edit_line Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/05_immutable.cf | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/05_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/05_immutable.cf b/tests/acceptance/10_files/unsafe/05_immutable.cf new file mode 100644 index 0000000000..020058f9c5 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/05_immutable.cf @@ -0,0 +1,93 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using edit_line +# attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/05_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + content => "I'm immutable"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle edit_line insert_line +{ + insert_lines: + "But agent can override"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the edit_line attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + edit_line => insert_line; +} + +bundle agent check +{ + vars: + "expected" + string => "I'm immutable$(const.n)But agent can override$(const.n)"; + "actual" + string => readfile("$(global.testfile)"); + + classes: + "ok" + expression => strcmp("$(actual)", "$(expected)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected:$(const.n)'$(expected)',$(const.n)actual:$(const.n)'$(actual)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; + "$(global.testfile).cf-before-edit" + delete => tidy; +} From 9f2b6890d2ec8cb86427a57dd7db8a1554ca9c43 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:54:34 +0200 Subject: [PATCH 30/38] Added acceptance test for immutable with edit_xml Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/06_immutable.cf | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/06_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/06_immutable.cf b/tests/acceptance/10_files/unsafe/06_immutable.cf new file mode 100644 index 0000000000..497dfb2450 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/06_immutable.cf @@ -0,0 +1,93 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using edit_xml attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/06_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + content => 'I\'m immutable'; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle edit_xml edit_foo +{ + set_text: + "But agent can override" + select_xpath => "/foo"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the edit_xml attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + edit_xml => edit_foo; +} + +bundle agent check +{ + vars: + "expected" + string => '$(const.n)But agent can override$(const.n)'; + "actual" + string => readfile("$(global.testfile)"); + + classes: + "ok" + expression => strcmp("$(actual)", "$(expected)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected: '$(expected)', actual: '$(actual)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; + "$(global.testfile).cf-before-edit" + delete => tidy; +} From 23b65194c2d3927d9bd54d85da491ee3f14bb095 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:55:38 +0200 Subject: [PATCH 31/38] Added acceptance test for immutable with perms Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/07_immutable.cf | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/07_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/07_immutable.cf b/tests/acceptance/10_files/unsafe/07_immutable.cf new file mode 100644 index 0000000000..446a433563 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/07_immutable.cf @@ -0,0 +1,85 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using perms attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/07_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true", + perms => m("600"); + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the perms attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + perms => m("644"); +} + +bundle agent check +{ + vars: + "expected" + string => "644"; + "actual" + string => string_tail(filestat("$(global.testfile)", "modeoct"), "3"); + + classes: + "ok" + expression => strcmp("$(actual)", "$(expected)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected: '$(expected)', actual: '$(actual)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From 6b0b70db76bf1c8dd79401aab97d11e508b6ecd2 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:56:38 +0200 Subject: [PATCH 32/38] Added acceptance test for immutable with touch Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/08_immutable.cf | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/08_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/08_immutable.cf b/tests/acceptance/10_files/unsafe/08_immutable.cf new file mode 100644 index 0000000000..dc50046a29 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/08_immutable.cf @@ -0,0 +1,82 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using touch attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/08_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + commands: + "touch -d @0 $(global.testfile)" + contain => in_shell; + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the touch attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + touch => "true"; +} + +bundle agent check +{ + vars: + "not_expected" + string => "0"; + "actual" + string => filestat("$(global.testfile)", "mtime"); + + classes: + "ok" + expression => not(strcmp("$(actual)", "$(not_expected)")); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Not expecting $(not_expected), got $(actual)"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From 587ed51e676823fb58a288ee78c2e45d0dfb3ffb Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:57:22 +0200 Subject: [PATCH 33/38] Added acceptance test for immutable with edit_template Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/09_immutable.cf | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/09_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/09_immutable.cf b/tests/acceptance/10_files/unsafe/09_immutable.cf new file mode 100644 index 0000000000..5aa825751f --- /dev/null +++ b/tests/acceptance/10_files/unsafe/09_immutable.cf @@ -0,0 +1,94 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using edit_template +# attribute with mustache +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/09_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true", + content => "Hello olehermanse!"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the edit_template attribute with mustache"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + vars: + "object" + data => '{ "user": "larsewi" }'; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + template_method => "inline_mustache", + edit_template_string => "Hello {{{user}}}!", + template_data => @(object); +} + +bundle agent check +{ + vars: + "expected" + string => "Hello larsewi!"; + "actual" + string => readfile("$(global.testfile)"); + + classes: + "ok" + expression => strcmp("$(actual)", "$(expected)"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expecting '$(expected)', got '$(actual)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; + "$(global.testfile).cf-before-edit" + delete => tidy; +} From 4369f2f58260899ce8995dbc8d9734545143be18 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:58:08 +0200 Subject: [PATCH 34/38] Added acceptance test for immutable with acl Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/10_immutable.cf | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/10_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/10_immutable.cf b/tests/acceptance/10_files/unsafe/10_immutable.cf new file mode 100644 index 0000000000..9e588d7232 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/10_immutable.cf @@ -0,0 +1,98 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using acl attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/10_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +body acl acl_user_root_rw +{ + acl_method => "append"; + aces => { "user:root:rw" }; +} + +body acl acl_user_root_rwx +{ + acl_method => "append"; + aces => { "user:root:rwx" }; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true", + acl => acl_user_root_rw; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the acl attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + acl => acl_user_root_rwx; + +} + +bundle agent check +{ + vars: + "expected" + string => ".*user:root:rwx.*"; + "acls" + slist => getacls("$(global.testfile)", "access"); + + classes: + "ok" + expression => some("$(expected)", "acls"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expecting output matching '$(expected)', got '$(acls)'"; +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable"; + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable" }; +} From 40a45473849ba508df30ad66d4aabc4d49dc0882 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:58:58 +0200 Subject: [PATCH 35/38] Added acceptance test for immutable with transformer Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/11_immutable.cf | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/11_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/11_immutable.cf b/tests/acceptance/10_files/unsafe/11_immutable.cf new file mode 100644 index 0000000000..6ae49ebb67 --- /dev/null +++ b/tests/acceptance/10_files/unsafe/11_immutable.cf @@ -0,0 +1,104 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using transformer +# attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/11_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the transformer attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + vars: + "gzip_path" + string => ifelse( + isexecutable("/bin/gzip"), + "/bin/gzip", + "/usr/bin/gzip" + ); + + files: + "$(global.testfile)" + fsattrs => set_immutable, + transformer => "$(gzip_path) $(this.promiser)"; + +} + +bundle agent check +{ + classes: + "original_exists" + expression => fileexists("$(global.testfile)"); + "transformed_exists" + expression => fileexists("$(global.testfile).gz"); + "ok" + expression => "!original_exists&transformed_exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected file '$(global.testfile)' to not exists $(with)" + with => ifelse("!original_exists", "and it does not", "but it does"); + "Expected file '$(global.testfile).gz' to exists $(with)" + with => ifelse("transformed_exists", "and it does", "but it does not"); +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "is mutable 1", + if => fileexists("$(global.testfile)"); + "chattr -i $(global.testfile).gz" + contain => in_shell, + handle => "is mutable 2", + if => fileexists("$(global.testfile).gz"); + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "is mutable 1" }; + files: + "$(global.testfile).gz" + delete => tidy, + depends_on => { "is mutable 2" }; +} From b0ce49be9bdd450bd77b3f8df6d7205c3486f58f Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:59:52 +0200 Subject: [PATCH 36/38] Added acceptance test for immutable with rename Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- .../10_files/unsafe/12_immutable.cf | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/acceptance/10_files/unsafe/12_immutable.cf diff --git a/tests/acceptance/10_files/unsafe/12_immutable.cf b/tests/acceptance/10_files/unsafe/12_immutable.cf new file mode 100644 index 0000000000..a1546db22b --- /dev/null +++ b/tests/acceptance/10_files/unsafe/12_immutable.cf @@ -0,0 +1,99 @@ +############################################################################## +# +# Test that agent can override the immutable bit when using rename attribute +# +############################################################################## + +body common control +{ + inputs => { "../../default.cf.sub" }; + bundlesequence => { default("$(this.promise_filename)") }; + version => "1.0"; +} + +bundle agent global +{ + vars: + "testfile" + string => "/tmp/12_immutable.txt"; +} + +body fsattrs set_immutable +{ + immutable => "true"; +} + +bundle agent init +{ + files: + "$(global.testfile)" + create => "true"; + + commands: + "chattr +i $(global.testfile)" + contain => in_shell; +} + +body rename nuke +{ + disable => "true"; + disable_suffix => ".nuked"; +} + +bundle agent test +{ + meta: + "description" -> { "CFE-1840", "ENT-10961" } + string => "Test that agent can override the immutable bit on a file while using the rename attribute"; + + "test_skip_unsupported" + string => "hpux|aix|solaris|windows"; + + files: + "$(global.testfile)" + fsattrs => set_immutable, + rename => nuke; +} + +bundle agent check +{ + classes: + "original_exists" + expression => fileexists("$(global.testfile)"); + "nuked_exists" + expression => fileexists("$(global.testfile).nuked"); + "ok" + expression => "!original_exists&nuked_exists"; + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; + any:: + "Expected file '$(global.testfile)' to not exists $(with)" + with => ifelse("!original_exists", "and it does not", "but it does"); + "Expected file '$(global.testfile).nuked' to exists $(with)" + with => ifelse("nuked_exists", "and it does", "but it does not"); +} + +bundle agent destroy +{ + commands: + "chattr -i $(global.testfile)" + contain => in_shell, + handle => "orig is mutable", + if => fileexists("$(global.testfile)"); + "chattr -i $(global.testfile).nuked" + contain => in_shell, + handle => "nuked is mutable", + if => fileexists("$(global.testfile).nuked"); + + files: + "$(global.testfile)" + delete => tidy, + depends_on => { "orig is mutable" }; + "$(global.testfile).nuked" + delete => tidy, + depends_on => { "nuked is mutable" }; +} From c61fe039c88d8605c80eb27902a5b35111823a23 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 11:26:43 +0200 Subject: [PATCH 37/38] Added context to log messages in OverrideImmutableCommit Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/libpromises/override_fsattrs.c b/libpromises/override_fsattrs.c index 4652396fae..b4cae2f6d4 100644 --- a/libpromises/override_fsattrs.c +++ b/libpromises/override_fsattrs.c @@ -6,6 +6,7 @@ #include #include #include +#include bool OverrideImmutableBegin( const char *orig, char *copy, size_t copy_len, bool override) @@ -70,7 +71,9 @@ bool OverrideImmutableCommit( struct stat sb; if (lstat(orig, &sb) == -1) { - Log(LOG_LEVEL_ERR, "Failed to stat file '%s'", orig); + Log(LOG_LEVEL_ERR, + "Failed to stat file '%s' during immutable operations", + orig); unlink(copy); return false; } @@ -78,7 +81,7 @@ bool OverrideImmutableCommit( if (chmod(copy, sb.st_mode) == -1) { Log(LOG_LEVEL_ERR, - "Failed to change mode bits on file '%s' to %04jo: %s", + "Failed to change mode bits on file '%s' to %04jo during immutable operations: %s", orig, (uintmax_t) sb.st_mode, GetErrorStr()); @@ -111,11 +114,20 @@ FSAttrsResult TemporarilyClearImmutableBit( } else { - Log((res == FS_ATTRS_FAILURE) ? LOG_LEVEL_ERR - : LOG_LEVEL_VERBOSE, - "Failed to temporarily clear immutable bit for file '%s': %s", - filename, - FSAttrsErrorCodeToString(res)); + if (res == FS_ATTRS_FAILURE) + { + Log(LOG_LEVEL_ERR, + "Failed to temporarily clear immutable bit for file '%s': %s", + filename, + FSAttrsErrorCodeToString(res)); + } + else + { + Log(LOG_LEVEL_VERBOSE, + "Could not temporarily clear immutable bit for file '%s': %s", + filename, + FSAttrsErrorCodeToString(res)); + } } } else From 7dc51ff5bc34144f65b6ce9b2d1fb25e2a180172 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 12:11:40 +0200 Subject: [PATCH 38/38] Make it more clear what the override argument does Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/override_fsattrs.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h index fc0588bcf3..e6502736a9 100644 --- a/libpromises/override_fsattrs.h +++ b/libpromises/override_fsattrs.h @@ -36,8 +36,8 @@ * @param orig The original file (may be immutable) * @param copy Updated to contain the filename of the mutable copy * @param copy_len The size of the buffer to store the filename of the copy - * @param override Whether to actually do override (original filename is - * copied to copy buffer if false) + * @param override Whether to actually do override (i.e. temporarily clear it). + * The original filename is copied to copy-buffer if false * @return false in case of failure */ bool OverrideImmutableBegin( @@ -48,7 +48,7 @@ bool OverrideImmutableBegin( * replaces it with the mutated copy * @param orig The original file (may be immutable) * @param copy The mutated copy to replace the original - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @param abort Whether to abort the override * @return false in case of failure * @note The immutable bit is reset to it's original state @@ -60,7 +60,7 @@ bool OverrideImmutableCommit( * @brief Change mode on an immutable file * @param filename Name of the file * @param mode The file mode - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @return false in case of failure * @note It uses safe_chmod() under the hood */ @@ -71,7 +71,7 @@ bool OverrideImmutableChmod(const char *filename, mode_t mode, bool override); * new to the old * @param old_filename Filename of the old file * @param new_filename Filename of the new file - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @return false in case of failure */ bool OverrideImmutableRename( @@ -80,7 +80,7 @@ bool OverrideImmutableRename( /** * @brief Delete immutable file * @param filename Name of the file to delete - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @return false in case of failure */ bool OverrideImmutableDelete(const char *filename, bool override); @@ -89,7 +89,7 @@ bool OverrideImmutableDelete(const char *filename, bool override); * @brief Temporarily clears the immutable bit and changes access and * modification times of the inode * @param filename Name of the file to touch - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @param times Modification times (can be NULL) * @return false in case of failure */ @@ -99,7 +99,7 @@ bool OverrideImmutableUtime( /** * @brief Temporarily clears the immutable bit (best effort / no guarantees) * @param filename Name of the file to clear the immutable bit on - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @param is_immutable Whether or not the file actually was immutable * @return Result of clearing the immutable bit (no to be interpreted by the * caller) @@ -110,7 +110,7 @@ FSAttrsResult TemporarilyClearImmutableBit( /** * @brief Reset temporarily cleared immutable bit * @param filename Name of the file to clear the immutable bit on - * @param override Whether to actually do override + * @param override Whether to actually do override (i.e. temporarily clear it) * @param res The result from previously clearing it * @param is_immutable Whether or not the file actually was immutable */