Skip to content

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip] - #892

Merged
richm merged 3 commits into
mainfrom
fingerprint-write-to-file
Aug 6, 2026
Merged

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]#892
richm merged 3 commits into
mainfrom
fingerprint-write-to-file

Conversation

@spetrosi

@spetrosi spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Feature: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]

Reason: By default logs are printed to rsyslog. This change adds a possibility to write logs to a file on the system for the downstream users.

Result: For the upstream, this makes rsyslog log message more detailed. For the downstream - also writes logs to /var/log/sysroles.jsonl

@spetrosi spetrosi self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Ignore keyword(s) in the title.

⛔ Ignored keywords (1)
  • [citest_skip]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2664590b-30d9-456f-8bba-d83b75a86fd2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

The sr_fingerprint module now accepts structured role data, emits deterministic syslog fields, and optionally writes locked JSONL records with size trimming. Check mode returns fingerprint and prospective log details. Unit tests cover formatting, persistence, validation, failures, and timestamps.

Structured fingerprint logging

Layer / File(s) Summary
Fingerprint contract and execution flow
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module replaces sr_message with structured fingerprint arguments and outputs. It derives role, host, distribution, and Ansible data, formats syslog fields, supports check mode, and validates inputs. Tests cover record construction, formatting, helpers, check mode, errors, and timestamps.
Locked JSONL persistence and trimming
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module creates parent directories, appends JSONL records under a sidecar lock, preserves value types, and trims old records by size. Tests cover append behavior, directory creation, size limits, and multi-record trimming.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description Format ⚠️ Warning The description includes Reason, Result, and Signed-off-by with an email, but it does not include the required Enhancement: or Feature: section. Add an Enhancement: or Feature: section that describes the change, while retaining the existing required sections.
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits format and accurately describes the fingerprint logging change.
Description check ✅ Passed The description includes the feature, reason, and result, but it omits the required Issue Tracker Tickets section.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
library/sr_fingerprint.py (1)

130-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move standard imports before the Ansible import and run tox -e black,flake8.

This file has the Ansible import before the standard-library imports, so group the imports before changing anything else. Then run tox -e black,flake8 before committing, as the Python path requires the required checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 130 - 139, Reorder the imports in
sr_fingerprint.py so all standard-library imports precede the AnsibleModule
import, keeping the existing import set unchanged. Run tox -e black,flake8 and
resolve any reported formatting or lint issues before committing.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@library/sr_fingerprint.py`:
- Around line 283-287: Update _format_fingerprint_key_value to transform control
characters, including carriage return, newline, and NUL, into visible escaped
sequences before formatting and joining fingerprint key-value pairs. Preserve
the existing quoting and quote-doubling behavior, and add tests verifying
formatted output contains no raw CR, LF, or NUL characters.
- Around line 231-232: Update the log-writing flow around _trim_log_file to
calculate and pass only the actual overflow, cur_size + len(new_line) -
max_size, when trimming is needed. Define and enforce behavior for records
larger than max_size so appending cannot exceed the documented limit, and add
coverage for partial overflow and oversized records.
- Around line 220-237: Harden _write_jsonl_log against symlink substitution by
persisting only in a trusted, non-attacker-writable directory, opening both
log_file and lock_path with no-follow semantics, and validating that each opened
file is a regular file owned by the expected user before use. Ensure lock
acquisition is performed on the safely opened lock descriptor without trusting a
replaceable pathname, and add a regression test covering a symlinked lock
sidecar.

---

Nitpick comments:
In `@library/sr_fingerprint.py`:
- Around line 130-139: Reorder the imports in sr_fingerprint.py so all
standard-library imports precede the AnsibleModule import, keeping the existing
import set unchanged. Run tox -e black,flake8 and resolve any reported
formatting or lint issues before committing.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d5c6c97-b2c4-4c1f-a8a6-867fef2edded

📥 Commits

Reviewing files that changed from the base of the PR and between b5f70fb and 16186ec.

📒 Files selected for processing (2)
  • library/sr_fingerprint.py
  • tests/unit/test_sr_fingerprint.py

Comment thread library/sr_fingerprint.py
Comment on lines +220 to +237
def _write_jsonl_log(log_file, record, max_size=0):
_ensure_parent_dir(log_file)
new_line = _format_fingerprint_jsonl(record) + "\n"
lock_path = log_file + ".lock"
lock_fd = open(lock_path, "w")
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
try:
cur_size = os.path.getsize(log_file)
except OSError:
cur_size = 0
if max_size > 0 and cur_size + len(new_line) > max_size and cur_size > 0:
_trim_log_file(log_file, len(new_line))
with open(log_file, "a") as log_fd:
log_fd.write(new_line)
finally:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
lock_fd.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Prevent symlink attacks on the log and lock paths.

If a privileged play writes to a configured path in an attacker-writable directory, a local user can replace <log_file>.lock with a symlink. Line 224 follows that symlink and truncates its target before flock runs. Line 233 also follows a substituted log_file.

Use a trusted, non-attacker-writable directory for persistence. Open both files with no-follow semantics and verify regular-file ownership before use. A no-follow open alone is not sufficient if an attacker can replace the lock pathname between writers.

Add a regression test for a symlinked lock sidecar.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 223-223: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(lock_path, "w")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)


[warning] 232-232: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(log_file, "a")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 220 - 237, Harden _write_jsonl_log
against symlink substitution by persisting only in a trusted,
non-attacker-writable directory, opening both log_file and lock_path with
no-follow semantics, and validating that each opened file is a regular file
owned by the expected user before use. Ensure lock acquisition is performed on
the safely opened lock descriptor without trusting a replaceable pathname, and
add a regression test covering a symlinked lock sidecar.

Source: Linters/SAST tools

Comment thread library/sr_fingerprint.py
Comment on lines +231 to +232
if max_size > 0 and cur_size + len(new_line) > max_size and cur_size > 0:
_trim_log_file(log_file, len(new_line))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Trim only the actual overflow and handle oversized records.

_trim_log_file() receives len(new_line), but the required space is cur_size + len(new_line) - max_size. When the file is below the limit and records have different lengths, this removes more complete records than required. If new_line alone exceeds max_size, the function appends it and exceeds the documented maximum.

Calculate the overflow before trimming. Reject or explicitly define the behavior for a single record larger than max_log_size. Add tests for partial overflow and an oversized record.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 232-232: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(log_file, "a")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 231 - 232, Update the log-writing
flow around _trim_log_file to calculate and pass only the actual overflow,
cur_size + len(new_line) - max_size, when trimming is needed. Define and enforce
behavior for records larger than max_size so appending cannot exceed the
documented limit, and add coverage for partial overflow and oversized records.

Comment thread library/sr_fingerprint.py
Comment on lines +283 to +287
def _format_fingerprint_key_value(field, value):
text = "" if value is None else str(value)
if any(char in text for char in ' "='):
return '%s="%s"' % (field, text.replace('"', '""'))
return "%s=%s" % (field, text)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Escape control characters in syslog fields.

role_name, role_path, and distribution values can contain control characters. The current formatter preserves CR, LF, and NUL because it only handles spaces, quotes, and =. A newline splits the formatted fingerprint into multiple log lines and can inject misleading syslog fields.

Escape control characters to visible sequences before joining the key-value pairs. Add tests that assert formatted output contains no raw CR, LF, or NUL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 283 - 287, Update
_format_fingerprint_key_value to transform control characters, including
carriage return, newline, and NUL, into visible escaped sequences before
formatting and joining fingerprint key-value pairs. Preserve the existing
quoting and quote-doubling behavior, and add tests verifying formatted output
contains no raw CR, LF, or NUL characters.

Comment thread library/sr_fingerprint.py
os.fchmod(fd, stat.S_IMODE(orig_stat.st_mode))
try:
os.fchown(fd, orig_stat.st_uid, orig_stat.st_gid)
except OSError:
Comment thread library/sr_fingerprint.py
except BaseException:
try:
os.unlink(tmp_path)
except OSError:
for path in (log_file, log_file + ".lock"):
try:
os.unlink(path)
except OSError:
@spetrosi spetrosi changed the title feat: Write roles fingerprints to /var/log/sysroles.jsonl feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip] Aug 6, 2026
Feature: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]

Reason: By default logs are printed to rsyslog. This change adds a possibility to write logs to a file on the system for the downstream users.

Result: For the upstream, this makes rsyslog log message more detailed. For the downstream - also writes logs to /var/log/sysroles.jsonl
Signed-off-by: Sergei Petrosian <spetrosi@redhat.com>
@spetrosi
spetrosi force-pushed the fingerprint-write-to-file branch from 16186ec to 3158d71 Compare August 6, 2026 15:06
The sr_fingerprint module was rewritten to accept structured parameters
(status, role_name, role_path, etc.) instead of a free-form sr_message.
Update the role tasks and tests to match the new module interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@spetrosi

spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

[citest]

The fingerprint commit inadvertently replaced this file with a minimal
template, losing role-specific test dependencies. Restore the original.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@spetrosi

spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

[citest]

@richm
richm merged commit 1c2836a into main Aug 6, 2026
13 of 14 checks passed
@richm
richm deleted the fingerprint-write-to-file branch August 6, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants