Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include specific UID and GID checks in modules.file.check_perms #62792

Merged
merged 3 commits into from
Oct 7, 2022

Conversation

rhodesn
Copy link
Contributor

@rhodesn rhodesn commented Oct 1, 2022

If the same username exists in more than one nsswitch database, it's possible to end up in a situation where users can be associated with more than one UID.

What does this PR do?

The PR updates the file.check_perms function to check whether the UID and GID values of the file match the function arguments for user and group should they be IDs.

The PR also attempts to reduce the number of calls to get_user and get_group which result in repeat calls to stat. Instead use a single "post" file.stats call and compare user/group/mode changes using that.

What issues does this PR fix or reference?

Fixes: #62818

Previous Behavior

Files owned by a user who can be mapped to two UIDs, but have file permissions for the "wrong" UID will not be updated by file.check_perms. This is due to file.check_perms only comparing ownership by names, and not also IDs.

Under normal circumstances, if you supply an ID for user: in file.directory, and that ID either represents a non-existent user, or maps to a username via file.uid_to_user, then salt will correctly update file and directory permissions recursively. However if you end up in the unique situation where during a migration period, a username is present in two name databases but with different UIDs, salt will compare the output from stat (which returns the correct username but with the "previous" UIDs) for the file only against a username due to uid_to_user(name) being used. This results in the UID owning the file not being updated.

Example where file uid wont get updated even if a uid is supplied to file.directory:

% cat srv/salt/common.sls
passwd-cache-reset:
  file.absent:
    - name: /etc/passwd.cache

nsswitch-reset:
  file.line:
    - name: /etc/nsswitch.conf
    - mode: replace
    - content: "passwd:         files"
    - match: "^passwd:"

dir-absent:
  file.absent:
    - name: /test

testing-create:
  user.present:
    - name: testing
    - uid: 2000
    - usergroup: true

testfile:
  file.managed:
    - name: /test/testfile
    - makedirs: true
    - user: testing
    - group: testing
    - replace: false
    - require:
      - file: dir-absent
      - user: testing-create

libnss-cache:
  pkg.installed

nsswitch-update:
  file.line:
    - name: /etc/nsswitch.conf
    - mode: replace
    - content: "passwd:         cache files"
    - match: "^passwd:"

/etc/passwd.cache:
  file.managed:
    - contents: |
        testing:x:8000:2000::/home/testing:/bin/sh

file_stats_pre:
  module.run:
    - file.stats:
      - path: /test/testfile
    - require:
      - file: /etc/passwd.cache

dir-by-user-new-id:
  file.directory:
    - name: /test
    - user: 8000
    - recurse:
      - user
    - require:
      - file: /etc/passwd.cache

user_info_post:
  module.run:
    - user.info:
      - name: testing
    - require:
      - file: /etc/passwd.cache

file_stats_post:
  module.run:
    - file.stats:
      - path: /test/testfile
    - require:
      - file: testfile


# salt-call --version
salt-call 3006.0 (Sulfur)

# salt-call state.apply common;
local:
----------
          ID: passwd-cache-reset
    Function: file.absent
        Name: /etc/passwd.cache
      Result: True
     Comment: Removed file /etc/passwd.cache
     Started: 09:32:01.306346
    Duration: 3.933 ms
     Changes:
              ----------
              removed:
                  /etc/passwd.cache
----------
          ID: nsswitch-reset
    Function: file.line
        Name: /etc/nsswitch.conf
      Result: True
     Comment: Changes were made
     Started: 09:32:01.310382
    Duration: 2.674 ms
     Changes:
              ----------
              diff:
                  ---
                  +++
                  @@ -1,4 +1,4 @@
                  -passwd:         cache files
                  +passwd:         files
                   group:          files
                   shadow:         files
                   gshadow:        files
----------
          ID: dir-absent
    Function: file.absent
        Name: /test
      Result: True
     Comment: Removed directory /test
     Started: 09:32:01.313207
    Duration: 0.577 ms
     Changes:
              ----------
              removed:
                  /test
----------
          ID: testing-create
    Function: user.present
        Name: testing
      Result: True
     Comment: User testing is present and up to date
     Started: 09:32:01.314274
    Duration: 11.486 ms
     Changes:
----------
          ID: testfile
    Function: file.managed
        Name: /test/testfile
      Result: True
     Comment: Empty file
     Started: 09:32:01.326149
    Duration: 1.756 ms
     Changes:
              ----------
              group:
                  testing
              new:
                  file /test/testfile created
              user:
                  testing
----------
          ID: libnss-cache
    Function: pkg.installed
      Result: True
     Comment: All specified packages are already installed
     Started: 09:32:02.181527
    Duration: 23.372 ms
     Changes:
----------
          ID: nsswitch-update
    Function: file.line
        Name: /etc/nsswitch.conf
      Result: True
     Comment: Changes were made
     Started: 09:32:02.205086
    Duration: 2.051 ms
     Changes:
              ----------
              diff:
                  ---
                  +++
                  @@ -1,4 +1,4 @@
                  -passwd:         files
                  +passwd:         cache files
                   group:          files
                   shadow:         files
                   gshadow:        files
----------
          ID: /etc/passwd.cache
    Function: file.managed
      Result: True
     Comment: File /etc/passwd.cache updated
     Started: 09:32:02.207244
    Duration: 3.282 ms
     Changes:
              ----------
              diff:
                  New file
----------
          ID: file_stats_pre
    Function: module.run
      Result: True
     Comment: file.stats: Success
     Started: 09:32:02.212124
    Duration: 0.823 ms
     Changes:
              ----------
              file.stats:
                  ----------
                  atime:
                      1664616721.326989
                  ctime:
                      1664616721.326989
                  gid:
                      2000
                  group:
                      testing
                  inode:
                      1318636
                  mode:
                      0644
                  mtime:
                      1664616721.326989
                  size:
                      0
                  target:
                      /test/testfile
                  type:
                      file
                  uid:
                      2000
                  user:
                      testing
----------
          ID: dir-by-user-new-id              <---- note changes are returned
    Function: file.directory
        Name: /test
      Result: True
     Comment: Directory /test updated
     Started: 09:32:02.213108
    Duration: 1.702 ms
     Changes:
              ----------
              /test:
                  ----------
                  user:
                      8000
              /test/testfile:
                  ----------
                  user:
                      8000
----------
          ID: user_info_post
    Function: module.run
      Result: True
     Comment: user.info: Success
     Started: 09:32:02.214970
    Duration: 0.928 ms
     Changes:
              ----------
              user.info:
                  ----------
                  fullname:
                  gid:
                      2000
                  groups:
                      - testing
                  home:
                      /home/testing
                  homephone:
                  name:
                      testing
                  other:
                  passwd:
                      x
                  roomnumber:
                  shell:
                      /bin/sh
                  uid:
                      8000
                  workphone:
----------
          ID: file_stats_post
    Function: module.run
      Result: True
     Comment: file.stats: Success
     Started: 09:32:02.216141
    Duration: 0.717 ms
     Changes:
              ----------
              file.stats:
                  ----------
                  atime:
                      1664616721.326989
                  ctime:
                      1664616721.326989
                  gid:
                      2000
                  group:
                      testing
                  inode:
                      1318636
                  mode:
                      0644
                  mtime:
                      1664616721.326989
                  size:
                      0
                  target:
                      /test/testfile
                  type:
                      file
                  uid:                      <---- but not actually implemented
                      2000
                  user:
                      testing

Summary for local
-------------
Succeeded: 12 (changed=10)
Failed:     0
-------------
Total states run:     12
Total run time:   53.301 ms

When using:

dir-by-user-new-id:
  file.directory:
    - name: /test
    - user: testing                  <---- username this time
    - recurse:
      - user
    - require:
      - file: /etc/passwd.cache

The file.directory state returns changes: {}, which makes sense as the usernames match even though the uid doesn't.

Previously file.check_perms attempted to retrieve usernames via file.uid_to_user. An empty check was done on the return value which would signify a username not being present on the system - a hard failure as os.chown takes IDs, but no username means no ID. The PR changes this so that username checks are left to the file.chown function. The error string returned by file.chown is appended to ret["comments"] so it can be returned to the user should either the username or group name not be present.

New Behavior

Salt will check both file username and UID permissions against the supplied user and group arguments instead of trying to convert IDs to names and then comparing. This resolves an admittedly pretty unique situation.

I should add that I looked into making salt capable of accepting a username, and performing both name and ID checks using that, but those changes are more significant.

Merge requirements satisfied?

[NOTICE] Bug fixes or features added to Salt require tests.

Commits signed with GPG?

Yes/No

Please review Salt's Contributing Guide for best practices.

See GitHub's page on GPG signing for more information about signing commits with GPG.

If the same username exists in more than one nsswitch database, it's
possible to end up in a situation where users can be associated with
more than one UID. When this happens - files that are owned by the user,
but have the "previous" UID, will not have the UID updated by the
`file.directory` state when `recurse` is set to `["user", "group"]`.
This is due to `file.stats` returning the correct owning username,
causing `file.check_perms`, which only compares owner permissions by
name, to believe no permissions changes are required. If
`file.check_perms` was to check permissions via ID as well, it would
return the ID changes to be made.

The PR updates the `file.check_perms` function to also check whether the
UID and GID values of the file match the function arguments for `user`
and `group` should they be IDs.

Previously `file.check_perms` attempted to retrieve usernames via
`file.uid_to_user`. An empty check was done on the return value which
would signify a username not being present on the system - a hard
failure as `os.chown` takes IDs, but no username means no ID. The PR
changes this so that username checks are left to the `file.chown`
function. The error string returned by `file.chown` is appended to
`ret["comments"]` so it can be returned to the user should either the
username or group name not be present.

The PR also attempts to reduce the number of calls to `get_user` and
`get_group` which result in repeat calls to stat. Instead use a single
"post" `file.stats` call and compare user/group/mode changes using that.

This can be reproduced with `libnss-cache` by defining a user with the
same name, but different UID in `/etc/passwd.cache` and
`/etc/group.cache`.
@rhodesn rhodesn requested a review from a team as a code owner October 1, 2022 13:55
@rhodesn rhodesn requested review from dwoz and removed request for a team October 1, 2022 13:55
While refactoring the `mode` update section the `else` statements were
removed which rendered the `if ...: pass` a NOP. The PR negates the
`if is_link and not follow_symlinks` so we only continue to change the
mode for non-links and link targets if `follow_symlinks == True`.
@rhodesn
Copy link
Contributor Author

rhodesn commented Oct 2, 2022

The mode refactor bit me here - gradually fixing CI failures.

@rhodesn rhodesn force-pushed the check_perms-allow-name-id branch from 6921f92 to d95607b Compare October 2, 2022 11:42
Copy link
Contributor

@Ch3LL Ch3LL left a comment

Choose a reason for hiding this comment

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

This will require a changelog file

garethgreenaway
garethgreenaway previously approved these changes Oct 4, 2022
twangboy
twangboy previously approved these changes Oct 4, 2022
@garethgreenaway
Copy link
Contributor

@nrhodes91 Just making sure you saw @Ch3LL's comment about needing a changelog on this one. Thanks!

@rhodesn
Copy link
Contributor Author

rhodesn commented Oct 5, 2022

Created issue #62818 in keeping with the preferred changelog format.

@rhodesn rhodesn dismissed stale reviews from twangboy and garethgreenaway via 799bae9 October 5, 2022 17:45
@rhodesn rhodesn force-pushed the check_perms-allow-name-id branch from d95607b to 799bae9 Compare October 5, 2022 17:45
@rhodesn
Copy link
Contributor Author

rhodesn commented Oct 5, 2022

Added changelog/62818.fixed - went with fixed as I think that matches best. Let me know if it should be changed 👍

This mirrors the user/group checks which set `perms["cuser"]` etc when
there are changes expected. These values are used to determine if we
need to return changes in `ret["changes"]`. Before this commit
`file.chec_perms` was returning `mode` changes for new files which
didn't match the original behaviour.
@rhodesn rhodesn force-pushed the check_perms-allow-name-id branch from 799bae9 to 7861126 Compare October 5, 2022 17:59
@Ch3LL Ch3LL added the Sulfur v3006.0 release code name and version label Oct 6, 2022
@garethgreenaway garethgreenaway merged commit 95ba656 into saltstack:master Oct 7, 2022
@welcome
Copy link

welcome bot commented Oct 7, 2022

Congratulations on your first PR being merged! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Sulfur v3006.0 release code name and version
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] It's not possible to set UID via file.check_perms when a user can be mapped to two UIDs
4 participants