General situation

  • There is a department of N different employees + M soulless machines (such as an integration server).
  • There are many hosts. A part is used for tests, others in combat, those and those can be grouped according to the tasks performed.
  • You need to control user access to hosts, keeping the configuration in one place. It may be necessary:
    • Grant the user access to a host or group by creating a user on the system and registering an ssh key.
    • Revoke access, for example by deleting an ssh key,
    • Enable or disable sudo ,
    • Limit sudo ,
    • Enable or disable NOPASSWD .

Current solution

Now for registration of users I use such a scheme (so far I’m trying only on two hosts and two users):

  • Own playbook and role in which standard modules are used:

    • user (creates a user, can pass a password hash created via passwd --method=SHA-512 )
    • authorized_key (the required public key is added to the created user’s .ssh / authorized_hosts)
    • Plus a custom task that creates /etc/sudoers.d/username :

        copy: content: "{{ item.key }} ALL=(ALL) NOPASSWD:ALL" dest: "/etc/sudoers.d/{{ item.key }}" owner: root group: root mode: 0400 with_dict: "{{ users_list }}" when: users_list is defined become: true 
  • Shared inventory files (same as for other playbooks)

  • Files group_vars/hostname.yaml , in which the public keys and user password hashes are stored. They are stored in the form of a dictionary:

     users_list: username: ssh_key: "ssh-rsa ..." password: "$6$..." comment: "User Name" 

This implementation forces me to keep a separate document for each host or group of hosts, in which data on users is repeated. This makes it extremely difficult to support.

Desired result

I would like to implement it like this:

The user_credentials.yaml file, one for all:

 user_credentials: username: ssh_key: "ssh-rsa ..." password: "$6$..." comment: "User Name" username_2: ... 

In each group_vars/hostname.yaml :

 users_list: username: sudoer: yes nopasswd: yes username_2: sudoer: yes nopasswd: no some_fired_employee: state: absent 

Now, when the rights change, you just need to change the config in one place and execute a playbook on the necessary group of hosts.

Problem

I do not understand how to write specific tasks so that they take values ​​from the necessary files. Somehow it is necessary to form a dictionary on the go, in which there will be data from user_credentials.yaml , filtered by the fact that the user is in the users_list , combined with the data from users_list .

    2 answers 2

    Accuracy in the solution that you see as ideal to write can be real, but it will definitely be cumbersome due to the nesting of lookup and checking various conditions.

    But if you slightly correct your vision of the ideal solution, then you can get by with an easy and elegant solution. And certainly it will be easier than the current one.

    Let me try to offer a few small cubes, of which you can easily choose the most suitable solution for you.


    Assume that you have a folder files \ peoples \ in which the folders ivanov and petrov lie. There are two files in the ivanov folder: key.pub and pass.txt

    In the files of the host we will have a record of the type:

     ans_users: - user: username: ivanov ssh_key_file: "files/people/ivanov/key.pub" password_file: "files/people/ivanov/pass.txt" - user: username: petrov ssh_key_file: "files/people/petrov/key.pub" password_file: "files/people/petrov/pass.txt" 

    And we will create accounts with a simple script:

     - name: adding users user: name={{ item.username}} password="{{ lookup('file', '"files/people/{{ item.username }}/key.pub"') }}" with_items: - "{{ ans_users }}" - name: add ssh keys authorized_key: user={{ item.username}} key="{{ lookup('file', '{{ item.ssh_key_file }}') }}" state=present with_items: - "{{ ans_users }}" 

    Agree that we have already greatly improved the readability of the script due to the fact that the file name is more readable than sha512_hash.

    Let's try to understand how willing you are to sacrifice over-centralization. You do not have any centralized user_credentials.yaml config file that describes all users. In my opinion, the loss is not great either: this file was not needed at all initially and the best proof of this is the fact that the host configs turned out to be exactly the same as in your ideal solution. This over-centralization is an unnecessarily rigid thing, and here it is useless.


    Moving refactor further. We have simplified the creation of users and the management of their keys, then we need to figure out how we will manage sudo.

    A few words about best practice. Instead of using the lineinfile module to constantly pick the sudoers file, it is recommended to manage groups of users. You will need only two groups: sudo with a password and passwordless sudo. Once set up and forgotten.

    I propose instead of TWO parameters (sudoers, password) to users in the config file simply to prescribe ONE parameter - user groups. The config becomes more compact, readable. The job code is also greatly simplified.


    Next, we consider the issues of modification of rights, in particular - removal.

    • Adding a new user is trivial
    • Modifying passwords, keys, sudo is trivial
    • Remove the rights from the same host group - we keep two files in files \ people with the key from which nobody knows the password and the password of the password unknown to anyone: just see in the config ssh_key_file: "files/people/null_key.pub" and it will be immediately understood that this user will not go to this group of hosts
    • Dismissal of an employee - in steps of the number of times we remove the rights from all groups of hosts, access is removed but there is rubbish left in the configs, let him lie down until the day of cleaning. On the day of cleaning, we launch a separate playbook, which takes the username, and then goes through all the hosts, deleting the account under the knife - and as soon as it works, we clean the configs from the user and the files directory.

    In general, that's all, perhaps. It seems to me that the general direction is understandable and then you will think up the details yourself. If I incorrectly guessed your introductory, try adding more details to the description of the problem, and show you what else can be offered.

    • Thanks, I will digest the information and try. In general, the idea of ​​decentralization is understandable and looks more flexible. Instead of clarifying the task, I would rather suggest the result obtained on the review. - Nick Volynkin ♦
    • github.com/AutomationWithAnsible/ansible-usermanage looks good, according to you? - Nick Volynkin ♦
    • @NickVolynkin I look at the code and find out fragments of my own roles;) Very curious thing, thanks for the link. - AK ♦
    • Here's another popular one: generic-users - Nick Volynkin ♦
    • And it seems that both do not do passwordless sudo, but this one does, but it is very inconvenient. github.com/debops/ansible-bootstrap/blob/… - Nick Volynkin ♦

    Look towards ldap + group + SSO

    • Discussed, LDAP is not possible on combat hosts. Yes, and a question about something else. - Nick Volynkin ♦
    • Try to write more detailed answers. Explain what is the basis of your statement? - korytoff