77

I'd like to allow anyone to list and read all files in my directory tree, but I don't want to make the files executable :

dir
  \subdir1
      file1
  \subdir2
      file2
  ...
  \subdirX
      fileX

The following task makes my directories and files readable, but it makes all the files executable as well:

- name: Make my directory tree readable
  file:
    path: dir
    mode: 0755
    recurse: yes

On the other hand, if I choose mode 0644, then all my files are not executable, but I'm not able to list my directories.

Is it possible to set mode 755 for all directories and 644 for all files in a directory tree?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
mykola
  • 1,038
  • 2
  • 8
  • 14

4 Answers4

116

Since version 1.8, Ansible supports symbolic modes. Thus, the following would perform the task you want:

- name: Make my directory tree readable
  file:
    path: dir
    mode: u=rwX,g=rX,o=rX
    recurse: yes

Because X (instead of x) only applies to directories or files with at least one x bit set.

the
  • 21,007
  • 11
  • 68
  • 101
Adrien Clerc
  • 2,636
  • 1
  • 21
  • 26
  • 2
    This is the best and shortest solution – Quanlong Dec 02 '15 at 07:42
  • 2
    IIRC, while this works, it will execute quite slowly because it's using `stat` to individually check every file/folder. Calling `chmod` on the box is much faster – Jeff Widman May 09 '16 at 20:42
  • 2
    `chmod` will use `stat(2)` too. You can see this with `strace`. It might be faster because ansible might implement this in python. – Mircea Vutcovici Jun 02 '16 at 11:44
  • 2
    Getting `FAILED! => {"changed": false, "failed": true, "msg": "recurse option requires state to be 'directory'", "path": "dir", "state": "absent"}` – borisdiakur Jul 02 '16 at 22:19
  • Read the message: the file you tried seems not to be a directory (regular file, socket, …). Anyway this is not the question, so you should try asking the question somewhere else. – Adrien Clerc Jul 05 '16 at 08:28
  • 10
    I wouldn't say this is a full solution. If you unarchive something with various permissions (e.g. all files and directories are set to 755), this won't do anything. Let's say you wanted to then change only the files to be 644, you can't since all of them have at least one x bit set. What is required in ansible is a way to recurse through directories, targeting only files and updating them accordingly, which doesn't seem possible without some workaround. – seeafish Nov 02 '16 at 14:33
  • 1
    @seeafish yes, indeed. However, the question was to set directory permissions to be world readable, so my answer seems to fit. Your problem is a reversed one. You have too lax permissions and want to restrict this. You may need to do it in two passes (first restrict, then relax what you want), or to use a small shell excerpt with `find` for example. – Adrien Clerc Nov 03 '16 at 09:14
  • Do notice the "files with at least one x bit set" part. Applying `u=rwX,g=rX,o=rX` to a `0744` file (executable by owner) makes it `0755` (executable by all). `touch file && chmod 0744 file && chmod u=rwX,g=rX,o=rX file && stat file | grep Access` – x-yuri Jan 24 '19 at 16:33
  • 1
    If one file has mode `740 -rwxr-----`, setting mode `u=rwX,g=rX,o=rX` with ansible, you will get `755 -rwxr-xr-x` instead the expected `644 -rw-r--r--`. Despite that this is not what you wanted, it will make the file executable by group and others with unwanted security problems. See the explanation in my Answer below. – wolfrevo Feb 15 '20 at 16:53
29

The Ansible file/copy modules don't give you the granularity of specifying permissions based on file type so you'd most likely need to do this manually by doing something along these lines:

- name: Ensure directories are 0755
  command: find {{ path }} -type d -exec chmod -c 0755 {} \;
  register: chmod_result
  changed_when: "chmod_result.stdout != \"\""

- name: Ensure files are 0644
  command: find {{ path }} -type f -exec chmod -c 0644 {} \;
  register: chmod_result
  changed_when: "chmod_result.stdout != \"\""

These would have the effect of recursing through {{ path }} and changing the permissions of every file or directory to the specified permissions.

ozeebee
  • 1,878
  • 2
  • 23
  • 26
Bruce P
  • 19,995
  • 8
  • 63
  • 73
  • 4
    Actually, this is a workaround, not a solution. The state of this task is always "changed", so it's not useful for running constantly to ensure that the configuration is consistent. – mykola Mar 05 '15 at 04:24
  • 6
    Semantics. Unless somebody is willing to write a module, shell script, etc. to do this and report back to Ansible whether anything has changed then this is about the only solution there is. The original question didn't specify a need for that, just the desire to recursively change permissions based on files vs. directories. – Bruce P Mar 05 '15 at 14:31
  • 1
    If the permissions are ok for the owner, you could also `mode: go=u-w` – Penz Nov 08 '15 at 17:22
  • 4
    @mykola You could try `chmod -c` and use a `changed_when` condition. – augurar Dec 15 '15 at 22:49
6

Due to a "inadequate" implementation Ansible supports symbolic modes only partially (see explanation below).

Other than using the command line chmod, setting mode to u=rwX,g=rX,o=rX with Ansible will not allways be sufficient to get your files set to 644. The resulting permissions will also depend on the original mode of the file!

As stated in the docs for chmod and already pointed out by some comments to other answers of this question: If file permission for either u, g, or o is executable, then X will set the file permission also to x.

For example. If one file has mode 740 -rwxr-----, setting mode u=rwX,g=rX,o=rX with ansible, you will get 755 -rwxr-xr-x instead the expected 644 -rw-r--r--. Despite that this is not what you wanted, it will make the file executable by group and others with unwanted security problems.

In those cases, with Ansible you will need two steps to set file the permissions to 644.

- file:
    path: /path/to/dir
    state: directory
    recurse: yes
    mode: '{{ item }}'
  loop:
    - '-x'
    - 'u=rwX,g=rX,o=rX'
  • Note that if you wanted mode 744 for files you'll need u=rwx,g=rX,o=rX (first x lowercase!). This will work in Ansible as for the current implementation BUT this is not the way command line chmod works. See **Symbolic nodes with* chmod below.

Why is this so:

Ansible states in the function _symbolic_mode_to_octal to include things like: u=rw-x+X,g=r-x+X,o=r-x+X. Nonetheless if the given mode is g=-x+X, ansible ignores the -x perm. The function _symbolic_mode_to_octal iterates over the given permissions, when it comes to X the function _get_octal_mode_from_symbolic_perms doesn't compare the requested permissions to the already applied but to the original ones, thus ignoring it:

This is probably a bug in Ansible.

The simplest and efficient way is to delegate to a shell command: As proposed in @BruceP's answer.

If for some reason you are averse to use 'workarounds' and need an ansible-ish way to solve the problem AND you don't care about performance you could try the following:

NOTE: This will take very, very long depending on the number of files and directories!

- name: example
  hosts: 192.168.111.123
  become: yes
  gather_facts: no
  vars:
    path_to_dir: /path/to/dir
    target_mode_for_directories: 755
    target_mode_for_files: 644
  tasks:
    - name: collect list of directories '{{ path_to_dir }}'
      find:
        paths: '{{ path_to_dir }}'
        recurse: yes
        file_type: directory
      register: found_directories
    - name: set collected directories to mode '{{ target_mode_for_directories }}'
      file:
        dest: '{{ item.path }}'
        mode: '{{ target_mode_for_directories }}'
      loop: '{{ found_directories.files }}'
    - name: collect list of files under '{{ path_to_dir }}'
      find:
        paths: '{{ path_to_dir }}'
        recurse: yes
        file_type: file
      register: found_files
    - name: set collected files to mode '{{ target_mode_for_files }}'
      file:
        dest: '{{ item.path }}'
        mode: '{{ target_mode_for_files }}'
      loop: '{{ found_files.files }}'

Symbolic modes with chmod

Remember that setting symbolic modes with chmod can be very tricky. See the following examples that simply differ on the order of lowercase and uppercase X, i.e. u=X,g=X,o=x (o=lowercase x) vs. u=x,g=X,o=X (u=lowercase x) which results in 001 ---------x vs. 111 ---x--x--x:

$ sudo chmod -R 000 path/to/file; \
  sudo chmod -R u=X,g=X,o=x path/to/file; \
  sudo find path/to/file -printf ""%03m" "%M" "%p\\n"";

001 ---------x path/to/file

$ sudo chmod -R 000 path/to/file; \
  sudo chmod -R u=x,g=X,o=X path/to/file; \
  sudo find path/to/file -printf ""%03m" "%M" "%p\\n"";

111 ---x--x--x path/to/file

This is due to the fact that the perms are processed first for u, then for g and last for o. In the first example X won't apply for files because there is no x perm. In the second case X will apply for files after u=x has been set, thus setting both g=x and o=x

wolfrevo
  • 6,651
  • 2
  • 26
  • 38
1

This worked for me:

- file:
    path: dir
    mode: a-x
    recurse: yes
- file:
    path: dir
    mode: u=rwX,g=rX,o=rX
    recurse: yes

Remove execution permission first from all, otherwise group and others get execution permission to files.

See chmod man pages about X-directive:

execute/search only if the file is a directory or already has execute permission for some user (X)

Also this works:

- shell: "chmod -R a-x,u=rwX,g=rX,o=rX dir"

For some reason combination of these two does not work:

- file:
    path: dir
    mode: a-x,u=rwX,g=rX,o=rX
    recurse: yes
Jukka A
  • 11
  • 1
  • When you say it does not work - add any errors that you see in the question. – somename Feb 05 '20 at 17:17
  • 1
    There is no errors. Files just get execution permissions when initial state is 777. – Jukka A Feb 06 '20 at 07:19
  • @JukkaA is right. Both when he points to the symbolic mode `X` and when he mentions that combinated symbolic perms don't work in Ansible. Please see the explanations in my answer. – wolfrevo Feb 16 '20 at 09:53