gantsign / ansible-role-oh-my-zsh

Ansible role for installing and configuring oh-my-zsh
https://galaxy.ansible.com/gantsign/oh-my-zsh
MIT License
165 stars 41 forks source link

Unable to write .zshrc while connected with unprivilegied user #91

Closed alorence closed 4 years ago

alorence commented 4 years ago

Hi,

I usually perform all administrative tasks on my servers with a user different from "root". Unfortunately, in such case, it is impossible to perform some tasks as another user using Ansible privilege escalation.

The task write .zshrc for users will fail if it is executed for any user when connected as unprivilegied user:

TASK [gantsign.oh-my-zsh : write .zshrc for users] **************************************************
fatal: [ec2-**-***-***-***.eu-west-3.compute.amazonaws.com]: FAILED! => {}

MSG:

Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chown: changing ownership of '/var/tmp/ansible-tmp-1569230087.9570866-96215917740751/': Operation not permitted
chown: changing ownership of '/var/tmp/ansible-tmp-1569230087.9570866-96215917740751/source': Operation not permitted
}). For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user

with playbook

- hosts: all
  remote_user: my_admin_user
  vars:
    users:
    - username: dummy
      oh_my_zsh:
        theme: sporty_256
        plugins:
          - git
          - themes
  roles:
    - gantsign.oh-my-zsh

The error came from the task:

- name: write .zshrc for users
  become: yes
  become_user: '{{ item.username }}'
  template:
    src: zshrc.j2
    dest: '~{{ item.username }}/.zshrc'
    backup: yes
    mode: 'u=rw,go=r'
  with_items: '{{ users }}'

In Ansible docs, the suggested workarounds are:

  1. Use pipelining (works for all python modules but copy, fetch, template)
  2. Install POSIX.1e filesystem acl support on the managed host
  3. Don’t perform an action on the remote machine by becoming an unprivileged user

I have enabled pipelining (1) in ansible.cfg, to ensure most of the tasks works well (cloning the repository in ~dummy/.oh-my-zsh can be done by user my_admin_user). But since the last task use template module to write .zshrc, it fails if remote_user is not root. I don't know how to do the (2) Obviously, the (3) should be in your hands...

I wanted to open a Pull Request, modifying the task and using owner: '{{ item.username }}' instead of become_user: '{{ item.username }}', but I discovered this was the case before and has been changed in 02c7f1684dba4d2cc15. I understand that setting the owner of file is easy, but setting the corresponding primary group is challenging since it is not always the same "word".

I discovered there is some unix commands that can determine the primary group of a user, but I don't know which way to follow to resolve this issue.

What do you think ?

freemanjp commented 4 years ago

@alorence, thank you for reporting this issue, sorry for the late reply. I think there are two ways to resolve this issue.

1) Run this specific role as a privileged user e.g.:

- hosts: servers
  roles:
    - role: gantsign.oh-my-zsh
      become: yes
      users:
        - username: example

Note: I haven't been able to fully test this but it should be the simplest solution.

2) Grant your non-privileged Ansible user sufficient permissions.

I tried the following instructions for testing using a non-privileged user with Molecule https://molecule.readthedocs.io/en/stable/examples.html#docker-with-non-privileged-user and I was able to successfully execute this role. Note: you may want to restrict the sudo permissions (the example grants ALL sudo permissions).

Unfortunately, Ansible doesn't provide a clean solution for this issue at the moment and the current implementation of this role has proven to be the least worst option.

alorence commented 4 years ago

Thank you very much for answering.

Unfortunately, Solution 1 is not applicable in my case. Remotes I work on forbid root SSH connection for security reasons.

The solution 2 may work, but I must admit I am confused with "privileged" and "non-privileged" terms. What does they mean ? The administrative user who performs all tasks is in "wheel" group (RedHat based systems) or "sudo" group (Debian based systems). What sould I do to grant this user the suficient permissions that will allow him to write a file in another user's home directory ?

freemanjp commented 4 years ago

FYI: You don't need to SSH to a remote as root if the user you use for your SSH connection has permissions to execute commands as if they are root (e.g. using sudo).

If you're coming from a Windows background think of a "privileged" user as a user with Administrator permissions and a "non-privileged" user as an ordinary user without Administrator permissions. Administrator permissions allow you to install software and edit other users files etc.

Most Linux distributions use sudo (super-user do) as a way of allowing a user with low permissions to perform actions as other users with higher/different permissions (e.g. root). This is normally what Ansible uses when you use become and become_user.

By default, users are not able to use sudo, access is granted by editing files in /etc/sudoers.d, editing the /etc/sudoers file or assigning the user to groups who have been granted permission to use sudo.

"Variable SUDO_GROUP depends on distribution wheel is used on centos:7." means if your Linux distribution has a default group for users with permission to use sudo to use that group name (e.g. the group name is wheel on centos:7 and sudo on Ubuntu:16.04).

Configuring specific permissions for sudo isn't easy, see https://www.sudo.ws/man/1.8.27/sudoers.man.html. If you're using sudo on you desktop machine you're almost certainly granted ALL permissions, which is a lot easier to setup.

For this role, you'll need permissions to install software using the package manager (e.g. zsh and git). You'll need permission to run git on behalf of the users you want to install Oh My Zsh for. Change file permissions for that user, change the default shell, and write the .zshrc.

One thing to be aware of is security professionals will object to installing Oh My Zsh on servers (particularly production). So if you need them to grant you extra permissions they'll likely say no.

alorence commented 4 years ago

Unfortunately, a non-root user cannot write .zshrc file inside home directory of another user, even if it is in sudo or wheel group. Cloning .oh-my-zsh diretory works, changing default shell works, but any module that upload a file and change permission will not, as explained in ansible docs:

Pipelining does not work for python modules involving file transfer (for example: copy, fetch, template), or for non-python modules

I re-checked this with a very simple example. I started a fresh Debian Stretch server on Amazon EC2. By default, this server comes with SSH root dissabled, and an admin user that can do anithing using passwordless sudo.

I ran this playbook:

---
- hosts: all
  remote_user: admin
  become: yes
  vars:
    ansible_become_password_default: ""
    users:
      - username: admin
        oh_my_zsh:
          theme: tjkirch
          plugins:
            - git
            - themes

      - username: random_user
        oh_my_zsh:
          theme: jtriley
          plugins:
            - debian
            - virtualenv

  roles:
    - create_users
    - gantsign.oh-my-zsh

with this custom role in roles/create_users/tasks/main.yml

---
- name: Create user {{ item.username }}
  user:
    state: present
    name: "{{ item.username }}"
    system: no
    append: yes
  with_items: "{{ users }}"

See the result:

ansible-playbook -i ec2.hosts setup_zsh.yml
PLAY [all] **********************************************

TASK [Gathering Facts] **********************************
ok: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com]

TASK [create_users : Create user {{ item.username }}] ****************
ok: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'admin', 'oh_my_zsh': {'theme': 'tjkirch', 'plugins': ['git', 'themes']}})
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'random_user', 'oh_my_zsh': {'theme': 'jtriley', 'plugins': ['debian', 'virtualenv']}})

TASK [gantsign.oh-my-zsh : install dependencies] ****************
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item=git)
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item=zsh)
 [WARNING]: Updating cache and auto-installing missing dependency: python-apt

 [WARNING]: Could not find aptitude. Using apt-get instead

TASK [gantsign.oh-my-zsh : clone oh-my-zsh for users] ****************
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'admin', 'oh_my_zsh': {'theme': 'tjkirch', 'plugins': ['git', 'themes']}})
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'random_user', 'oh_my_zsh': {'theme': 'jtriley', 'plugins': ['debian', 'virtualenv']}})

TASK [gantsign.oh-my-zsh : set permissions of oh-my-zsh for users] ****************
ok: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'admin', 'oh_my_zsh': {'theme': 'tjkirch', 'plugins': ['git', 'themes']}})
ok: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'random_user', 'oh_my_zsh': {'theme': 'jtriley', 'plugins': ['debian', 'virtualenv']}})

TASK [gantsign.oh-my-zsh : set default shell for users] ****************
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'admin', 'oh_my_zsh': {'theme': 'tjkirch', 'plugins': ['git', 'themes']}})
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'random_user', 'oh_my_zsh': {'theme': 'jtriley', 'plugins': ['debian', 'virtualenv']}})

TASK [gantsign.oh-my-zsh : write .zshrc for users] ****************
changed: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com] => (item={'username': 'admin', 'oh_my_zsh': {'theme': 'tjkirch', 'plugins': ['git', 'themes']}})
fatal: [ec2-15-188-47-86.eu-west-3.compute.amazonaws.com]: FAILED! => {}

MSG:

Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chown: changing ownership
of '/var/tmp/ansible-tmp-1569837824.9828672-165693995545428/': Operation not permitted
chown: changing ownership of '/var/tmp/ansible-tmp-1569837824.9828672-165693995545428/source': Operation not permitted
}). For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user

PLAY RECAP ****************
ec2-15-188-47-86.eu-west-3.compute.amazonaws.com : ok=7    changed=4    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

So apparently, a privileged user (admin in this case) cannot update the owner of the temp file to another unprivileged user.

When I encounter this issue in my own roles, I often update the task to use owner and group arguments of the template module.

freemanjp commented 4 years ago

Sorry, it doesn't look like I can fix this without breaking other users or greatly complicating the implementation, I suggest you fork the role.