From 7ab4abd47371c2cb66fd8055fb2b75a50313ffbc Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 28 Mar 2025 07:50:11 +0100 Subject: [PATCH 01/27] Added utility functions to override immutable bit Ticket: ENT-10961, CFE-1840 Signed-off-by: Lars Erik Wik --- libpromises/Makefile.am | 1 + libpromises/override_fsattrs.c | 260 +++++++++++++++++++++++++++++++++ libpromises/override_fsattrs.h | 140 ++++++++++++++++++ 3 files changed, 401 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..3f3487a4e9 --- /dev/null +++ b/libpromises/override_fsattrs.c @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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)", + orig, + ret, + copy_len); + return false; + } + return true; + } + + 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, bool override, bool abort) +{ + if (!override) + { + return true; + } + + if (abort) + { + return unlink(copy) == 0; + } + + struct stat sb; + if (lstat(orig, &sb) == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to stat file '%s' during immutable operations", + 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 during immutable operations: %s", + orig, + (uintmax_t) sb.st_mode, + GetErrorStr()); + unlink(copy); + return false; + } + + return OverrideImmutableRename(copy, orig, override); +} + +FSAttrsResult TemporarilyClearImmutableBit( + const char *filename, bool override, bool *was_immutable) +{ + if (!override) + { + return FS_ATTRS_FAILURE; + } + + FSAttrsResult res = FSAttrsGetImmutableFlag(filename, was_immutable); + if (res == FS_ATTRS_SUCCESS) + { + if (*was_immutable) + { + res = FSAttrsUpdateImmutableFlag(filename, false); + if (res == FS_ATTRS_SUCCESS) + { + Log(LOG_LEVEL_VERBOSE, + "Temporarily cleared immutable bit for file '%s'", + filename); + } + else + { + 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 + { + 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 res; +} + +void ResetTemporarilyClearedImmutableBit( + const char *filename, bool override, FSAttrsResult res, bool was_immutable) +{ + if (!override) + { + return; + } + + if ((res == FS_ATTRS_SUCCESS) && was_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 reset immutable bit after temporarily clearing it from file '%s': %s", + filename, + FSAttrsErrorCodeToString(res)); + } + } +} + +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) +{ + assert(old_filename != NULL); + assert(new_filename != NULL); + + 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': %s", + new_filename, + old_filename, + GetErrorStr()); + unlink(old_filename); + return false; + } + + ResetTemporarilyClearedImmutableBit( + new_filename, override, res_old, old_is_immutable); + + return true; +} + +bool OverrideImmutableDelete(const char *filename, bool override) +{ + assert(filename != NULL); + + bool is_immutable = false; + TemporarilyClearImmutableBit(filename, override, &is_immutable); + + return unlink(filename) == 0; +} + +bool OverrideImmutableUtime( + const char *filename, bool override, const struct utimbuf *times) +{ + assert(filename != NULL); + + bool is_immutable; + FSAttrsResult res = + TemporarilyClearImmutableBit(filename, override, &is_immutable); + + int ret = utime(filename, times); + if (ret == -1) + { + Log(LOG_LEVEL_ERR, + "Failed to update access and modification times of file '%s': %s", + filename, + GetErrorStr()); + } + + ResetTemporarilyClearedImmutableBit(filename, override, res, is_immutable); + + return ret == 0; +} diff --git a/libpromises/override_fsattrs.h b/libpromises/override_fsattrs.h new file mode 100644 index 0000000000..7f15a0a083 --- /dev/null +++ b/libpromises/override_fsattrs.h @@ -0,0 +1,140 @@ +/* + 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 +#include +#include +#include + +/** + * @brief This function begins a transaction block by creating a temporary + * mutable copy of the original file. You can modify the copy while the + * original file is left unmodified until an explicit call to + * OverrideImmutableCommit(). + * @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 the immutable bit should be temporarily cleared on + * the original file when replacing it with the temporary copy (later in + * OverrideImmutableCommit). Here, a uniquely generated filename is copied to + * copy-buffer if true. Otherwise the original filename. + * @return false in case of failure + * @note This function uses the random number generator to generate unique + * names for the temporary mutable copy of the file. You should make sure to + * seed the number generator with something like `srand(time(NULL));` to + * ensure a different sequence each time the program is run. + */ +bool OverrideImmutableBegin( + const char *orig, char *copy, size_t copy_len, bool override); + +/** + * @brief This function commits or aborts a transaction block by temporarily + * clearing the immutable bit of the original file and replacing 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 the immutable bit should be temporarily cleared on + * the original file when replacing it with the temporary copy. + * @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, bool abort); + +/** + * @brief Change mode on an immutable file + * @param filename Name of the file + * @param mode The file mode + * @param override Whether the immutable bit should be temporarily cleared + * while changing the mode + * @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 + * @param old_filename Filename of the old file + * @param new_filename Filename of the new file + * @param override Whether the immutable bit should be temporarily cleared + * on both files while renaming + * @return false in case of failure + */ +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 the immutable bit should be cleared before removing + * the file + * @return false in case of failure + */ +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 the immutable bit should be temporarily cleared + * before modifying the mtime + * @param times Modification times (can be NULL) + * @return false in case of failure + */ +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 (i.e. temporarily clear the + * immutable bit) + * @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 (i.e. re-set temporarily + * cleared immutable bit) + * @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 f08eb51ab3902429b98e17e64d4729f83ac70c4d Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 28 Jan 2025 15:15:00 +0100 Subject: [PATCH 02/27] 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 3ff16ba0d68f65c4355318b43c5596da324dad94 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Thu, 6 Feb 2025 16:08:33 +0100 Subject: [PATCH 03/27] 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..6ce64960f1 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 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 019d229f735a0b657dfd4615b05d1fdd740f33c0 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 12:59:47 +0200 Subject: [PATCH 04/27] Store override_immutable variable in stack frame 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 | 8 ++++++++ libpromises/eval_context.c | 19 ++++++++++++++++++- libpromises/eval_context.h | 4 ++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/cf-agent/verify_files.c b/cf-agent/verify_files.c index 6ce64960f1..995c5edee1 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -402,6 +402,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 whenever needed. */ + 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 */ { if ((a.create) || (a.touch)) @@ -705,6 +710,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)) { diff --git a/libpromises/eval_context.c b/libpromises/eval_context.c index 50a433dc11..3a8eba5520 100644 --- a/libpromises/eval_context.c +++ b/libpromises/eval_context.c @@ -1323,6 +1323,7 @@ static StackFrame *StackFrameNew(StackFrameType type, bool inherit_previous) frame->type = type; frame->inherits_previous = inherit_previous; frame->path = NULL; + frame->override_immutable = false; return frame; } @@ -2878,7 +2879,7 @@ void EvalContextFunctionCachePut(EvalContext *ctx, Rlist *args_copy = RlistCopy(args); Rlist *key = RlistPrepend(&args_copy, fp->name, RVAL_TYPE_SCALAR); - + FuncCacheMapInsert(ctx->function_cache, key, rval_copy); } @@ -3830,3 +3831,19 @@ const char *ToNormalRoot(const char *orig_path) return orig_path + chroot_len - 1; } + +void EvalContextOverrideImmutableSet(EvalContext *ctx, bool should_override) +{ + assert(ctx != NULL); + StackFrame *stack_frame = LastStackFrame(ctx, 0); + assert(stack_frame != NULL); + stack_frame->override_immutable = should_override; +} + +bool EvalContextOverrideImmutableGet(EvalContext *ctx) +{ + assert(ctx != NULL); + StackFrame *stack_frame = LastStackFrame(ctx, 0); + assert(stack_frame != NULL); + return stack_frame->override_immutable; +} diff --git a/libpromises/eval_context.h b/libpromises/eval_context.h index 6ad09c08b3..a8c935ca40 100644 --- a/libpromises/eval_context.h +++ b/libpromises/eval_context.h @@ -100,6 +100,7 @@ typedef struct } data; char *path; + bool override_immutable; } StackFrame; typedef enum @@ -128,6 +129,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 d9d9afc8cedc7ed62354293691e50bdacb60ae30 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:04:24 +0200 Subject: [PATCH 05/27] 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 | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cf-agent/verify_files.c b/cf-agent/verify_files.c index 995c5edee1..7c5996cea8 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -61,6 +61,7 @@ #include /* PrepareChangesChroot(), RecordFileChangedInChroot() */ #include #include +#include static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp); static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp); @@ -796,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)) @@ -803,13 +805,23 @@ 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 ) { @@ -824,9 +836,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 40cfca150da2c41078b4b491da09664202480ebd Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:07:29 +0200 Subject: [PATCH 06/27] 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 2d786d378a9e0ee320bea51fc59f032a9eaa6e53 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:08:41 +0200 Subject: [PATCH 07/27] 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 f9afaec8d974875f1df867a1b87db198981d9728 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:13:40 +0200 Subject: [PATCH 08/27] 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 7c5996cea8..a09bc875f8 100644 --- a/cf-agent/verify_files.c +++ b/cf-agent/verify_files.c @@ -990,7 +990,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 e1d949c9f8a09d33e630456f57880d0ea2471e9b Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:16:24 +0200 Subject: [PATCH 09/27] 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 ++++--- 1 file changed, 4 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); } } From 5108f93c77166bd6e85de4417e86bac9ff353063 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:18:13 +0200 Subject: [PATCH 10/27] 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 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 68043bea10..8612dbdac7 100644 --- a/cf-agent/verify_files_utils.c +++ b/cf-agent/verify_files_utils.c @@ -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 fb7842726d9a97cc2dc6dea0ccb6556bb799d6a8 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:19:55 +0200 Subject: [PATCH 11/27] 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 8612dbdac7..4d53c57d8f 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 d4e7957a99ad84e49aff35f63a2b7c93eee4d23b Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:21:37 +0200 Subject: [PATCH 12/27] 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 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cf-agent/verify_files_utils.c b/cf-agent/verify_files_utils.c index 4d53c57d8f..4b65cac6ff 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()); From 9f7ba9dbc53c632b8e54730b7194c1e48a1ae36d Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:24:17 +0200 Subject: [PATCH 13/27] 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..4af33f7d10 100644 --- a/cf-agent/acl_posix.c +++ b/cf-agent/acl_posix.c @@ -33,6 +33,8 @@ #include #include #include /* GetGroupID(), GetUserID() */ +#include +#include #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 9ae8e61c9480e31ced64866c47078a25499b0b97 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Tue, 1 Jul 2025 13:25:30 +0200 Subject: [PATCH 14/27] 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 44774e8a4c..e78e3672c1 100644 --- a/libpromises/evalfunction.c +++ b/libpromises/evalfunction.c @@ -988,7 +988,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) { @@ -1003,7 +1003,7 @@ static FnCallResult FnCallFindLocalUsers(EvalContext *ctx, ARG_UNUSED const Poli JsonDestroyMaybe(json, allocated); return FnFailure(); } - + JsonElement *parent = JsonObjectCreate(10); setpwent(); struct passwd *pw; @@ -1048,7 +1048,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'", @@ -1080,7 +1080,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; } } @@ -1105,13 +1105,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); } @@ -1409,7 +1409,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) { @@ -1433,7 +1433,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); @@ -1447,7 +1447,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 @@ -1474,7 +1474,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 } @@ -10849,7 +10849,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 752d9c0bc07f4781a1de8e8f77a02a5d6d30e707 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:45:45 +0200 Subject: [PATCH 15/27] 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 ad44ed18362da0cbde060c72f54ce987432b48af Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:47:16 +0200 Subject: [PATCH 16/27] 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 96fe049434deb1cdd312ad39e000dc0bf7a9543b Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:48:49 +0200 Subject: [PATCH 17/27] 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 d11dd07f9a7f5338adbf6f5d5e5a84f9ae795f6c Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:50:01 +0200 Subject: [PATCH 18/27] 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 b2d55f9995bc5270e79ec7009e7f9d4cf2df2413 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:51:19 +0200 Subject: [PATCH 19/27] 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 926c6846d00e899c7b98e25d3a9224c32feb4dbe Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:53:41 +0200 Subject: [PATCH 20/27] 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 a3883f17442090604cc9e713b822a9e5150707ac Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:54:34 +0200 Subject: [PATCH 21/27] 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 070fcc50c9e809028044a6b0183069072ac8a206 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:55:38 +0200 Subject: [PATCH 22/27] 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 d92d0c04627a99b561720d02d69d15cb45d6f3b5 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:56:38 +0200 Subject: [PATCH 23/27] 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 dbf11516d5a10fc4a35424f49e830f18ec0d958f Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:57:22 +0200 Subject: [PATCH 24/27] 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 003e524e63d6e8ab5cc4ba84a2c75709f8ea246d Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:58:08 +0200 Subject: [PATCH 25/27] 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 203ee40c70fd00d00a5abfd6b5a0dbb67994a588 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:58:58 +0200 Subject: [PATCH 26/27] 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 cd0bb6eedb7445f45dd2ad4ed7b9d59f509d90e4 Mon Sep 17 00:00:00 2001 From: Lars Erik Wik Date: Fri, 13 Jun 2025 10:59:52 +0200 Subject: [PATCH 27/27] 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" }; +}