[BUG] Default parameters for file.managed can cause unintended OS-breaking issues
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 15.7k
- Forks
- 5.6k
- Avg merge
- 2d 44m
- Merged PRs (30d)
- 80
Description
Description
When using file.managed, the default of follow_symlinks is true.
Which means that if (somehow) the intended destination is already symlinked to /dev/null - you will end up OVERWRITING the destination, leaving the symlink intact.
In our case, this happened due to someone using systemctl mask on a service, with a unit file residing in /etc/systemd/system.
What systemd does when masking, is create a symlink from /etc/systemd/system/<unit>.<type> to /dev/null.
The end result, was a broken /dev/null which can lead to plenty of (obvious and dangerous) problems.
Setup
systemd application service:
file.managed:
- name: /etc/systemd/system/application.service
- source: salt://application/files/systemd/system/application.service
- mode: "0644"
- user: root
- group: root
Steps to Reproduce the behavior
> systemctl mask application
Unit application.service does not exist, proceeding anyway.
Created symlink /etc/systemd/system/application.service → /dev/null.
... and run Salt with the above SLS.
Expected behavior
Unsure what the viable Expected Behaviour here is to "resolve" it, but here are some that comes to mind;
- The symlink should be removed, and replaced by a file (
/dev/nullbeing identified as anull device) - The output should cleanly be written to
/dev/nulland Saltstack should fail the operation with an error message
There is probably be legitimate use-cases where you want to follow a symlink and write to a file, but in that case Saltstack should identify that the destination is already a normal file (where mode should be applied) or a socket (where it should fail if mode is defined).
... maybe follow_symlinks being true is a bad default for file.managed?
Ansible for example, does not default to true. It used to (until they found out it was insane), and changed it a long time ago.
Additional Details
When simulating the same "approach" using Ansible's ansible.builtin.template module, the null device is not corrupted.
> mknod -m 0666 /tmp/null c 1 3
> ln -s /tmp/null /tmp/testfile
> echo "testing {{ ansible_host }}" > /tmp/testfile.tmpl
> ansible '*' -c local -i localhost, -m template -a 'src=/tmp/testfile.tmpl dest=/tmp/testfile mode=0644 owner=root group=root'
localhost | CHANGED => {
"changed": true,
"checksum": "9dd8f3c5506d4aea83c1327519a3883ac726871b",
"dest": "/tmp/testfile",
"gid": 0,
"group": "root",
"md5sum": "08426641dc49c48cdf4bf80330c3f708",
"mode": "0644",
"owner": "root",
"size": 18,
"src": "/root/.ansible/tmp/ansible-tmp-1685699801.9378633-4307-217502886448945/source",
"state": "file",
"uid": 0
}
> cat /tmp/testfile
testing localhost
> stat /tmp/null
File: /tmp/null
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 0,75 Inode: 154 Links: 1 Device type: 1,3
Access: (0666/crw-rw-rw-) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2023-06-02 11:51:49.390389817 +0200
Modify: 2023-06-02 11:51:49.390389817 +0200
Change: 2023-06-02 11:51:49.390389817 +0200
Birth: -
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the file.managed state entry point and reproduce the systemctl mask scenario described in the issue, where the destination points to /dev/null. Determine the safe handling for follow_symlinks and define completion by ensuring the null device is not corrupted while legitimate symlink behavior remains supported.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100