How to create a specific user on a specific host - ansible

There are several computers on the network, on each of them you need to create a user with a specific login and password.
I create users like this:
vars_prompt:
- name: "user_name"
prompt: "User name"
private: no
- name: "user_password"
prompt: "Enter a password for the user"
private: yes
encrypt: "md5_crypt"
confirm: yes
salt_size: 7
tasks:
- name: "add new user"
user:
name: "{{user_name}}"
password: "{{user_password}}"
shell: /bin/bash
Since there are many computers I don’t want to run a playbook a huge number of times. Ideally, I would like to implement the input of the list of hosts (computers) and the list of users. Password, in principle, you can do the same everywhere.

Loop the task
tasks:
- name: "add new user"
user:
name: "{{ item.user_name }}"
password: "{{ item.user_password }}"
shell: /bin/bash
loop: "{{ my_users }}"
and put the variable(s) my_users to host_vars
my_users:
- user_name: user1
user_password: password1
- user_name: user2
user_password: password2
Put common users to group_vars.
See Variable precedence: Where should I put a variable?
Use Ansible Vault to encrypt the passwords.

Here is an example of what you can try. Adapt to your needs.
Note: if the list of users is different for each host, just execute the playbook several times. Implementing this as a promptable play in ansible will just be a total pain and merely unusable.
In the example below, test1 and test2 are pointing to 2 docker containers I added in my demo_inventory.yml.
all:
hosts:
test1:
ansible_connection: docker
test2:
ansible_connection: docker
The hosts you enter will need to be correctly known by ansible for this to work.
This is the demo playbook test.yml
---
- name: Gather needed information
hosts: localhost
vars_prompt:
- name: hosts_entry
prompt: Enter comma separated list of hosts to target
private: false
- name: users_entry
prompt: Enter comma separated list of users to create
private: false
- name: user_password
prompt: Enter initial password applied to all users
encrypt: md5_crypt
confirm: true
salt_size: 7
tasks:
- name: Create a dynamic whatever_group with entered hosts
add_host:
name: "{{ item | trim }}"
groups:
- whatever_group
loop: "{{ hosts_entry.split(',') }}"
- name: Create a list of host for later reuse. Will be scoped to localhost
set_fact:
users_list: "{{ users_entry.split(',') }}"
- name: Store password for later reuse as vars_prompt are limited to play
set_fact:
user_password: "{{ user_password }}"
- name: Do the actual work
hosts: whatever_group
tasks:
- name: Make sure users are present
user:
name: "{{ item | trim }}"
password: "{{ hostvars['localhost'].user_password }}"
shell: /bin/bash
loop: "{{ hostvars['localhost'].users_list }}"
I created a play on localhost to gather the info from vars_prompt. In this play, I used add_host to create a whatever_group dynamically. Note the use of split to create list from a string with comma seperated elements in the input and of trim to remove the leading/trailing spaces (if user entered them). Since vars_prompt are limited in scope to the current play, I also used set_fact to get the users list and the password for future use.
On the next play, I target the whatever_group and run the user task. Note that since set_fact used previously scoped the variables to localhost, we have to use the hostvars magic variable to get the relevant information for the user loop and the password.
Here is the example run
$ ansible-playbook -i demo_inventory.yml test.yml
Enter comma separated list of hosts to target: test1, test2
Enter comma separated list of users to create: user1, user2, user3
Enter initial password applied to all users:
confirm Enter initial password applied to all users:
PLAY [Gather needed information] ***************************************************************
TASK [Gathering Facts] *************************************************************************
ok: [localhost]
TASK [Create a dynamic whatever_group with entered hosts] **************************************
changed: [localhost] => (item=test1)
changed: [localhost] => (item= test2)
TASK [Create a list of host for later reuse. Will be scoped to localhost] **********************
ok: [localhost]
TASK [Store password for later reuse as vars_prompt are limited to play] ***********************
ok: [localhost]
PLAY [Do the actual work] **********************************************************************
TASK [Gathering Facts] *************************************************************************
ok: [test1]
ok: [test2]
TASK [Make sure users are present] *************************************************************
changed: [test2] => (item=user1)
changed: [test1] => (item=user1)
changed: [test2] => (item= user2)
changed: [test1] => (item= user2)
changed: [test2] => (item= user3)
changed: [test1] => (item= user3)
PLAY RECAP *************************************************************************************
localhost : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Multiple hosts prompt is working but there's a problem with execution

This is my code:
---
- hosts: localhost
gather_facts: no
vars_prompt:
- name: server
prompt: "What is the hostname/ip you want to execute at?"
private: no
tasks:
- add_host:
name: "{{ server }}"
groups: dynamic_hosts
with_items: "{{ server.split(',') }}"
#### Dynamic Host
- hosts: dynamic_hosts
gather_facts: no
tasks:
- name: "Running task id"
command: id
and this is the behaviour:
What is the hostname you want to execute at?: 10.0.0.2, 10.0.0.3
PLAY [localhost] *******************************************************************************************************************************************************************************************************************
TASK [add_host] ********************************************************************************************************************************************************************************************************************
changed: [localhost] => (item= 10.0.0.2)
changed: [localhost] => (item= 10.0.0.3)
PLAY [dynamic_hosts] ***************************************************************************************************************************************************************************************************************
TASK [Running task id] ********************************************************************************************************************************************
fatal: [10.0.0.2, 10.0.0.3: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname 10.0.0.2, 10.0.0.3: Name or service not known\r\n", "unreachable": true}
to retry, use: --limit #/home/user/playbook.yaml
PLAY RECAP *************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0
10.0.0.2, 10.0.0.3 : ok=0 changed=0 unreachable=1 failed=0
So the input of multiple hosts is working properly but when I try to call the group in hosts basically it tries to "ssh 10.0.0.2, 10.0.0.3" and naturally it fails.
What am I missing here?
What I want to do is prompt the user for the hosts he wants to execute at, input
and then just execute the tasks to each host. I do not want to make use of an inventory file.
Is it possible? Thank you in advance
Working code:
---
- hosts: localhost
gather_facts: no
vars_prompt:
- name: server
prompt: "What is the hostname/ip you want to execute at?"
private: no
tasks:
- add_host:
name: "{{ item }}"
groups: dynamic_hosts
with_items: "{{ server.split(',') }}"
#### Dynamic Host
- hosts: dynamic_hosts
gather_facts: no
tasks:
- name: "Running task id"
command: id
thank you

Create password and write it to file

The idea is to generate user accounts over an API. Using default variables as the basic information:
---
students:
- Username: testuser1
E-Mail: student1#student.com
- Username: testuser2
E-Mail: student2#student.com
The Creating User role will then create all users with the API:
- name: "Creating User"
uri:
url: "https://URL/api/v1/users"
method: POST
headers:
Content-Type: application/json
Authorization: AUTH TOKEN
body:
name: "{{ item['Username'] }}"
email: "{{ item['E-Mail'] }}"
password: "{{ item['Password'] }}"
body_format: json
validate_certs: no
loop: "{{ students }}"
I can not find a way to generate a password for each user and write them to a file. Is there a way I can append a Password variable to each student item before the Creating User role? If so I could just write the default variable to a file as a last role.
I've played with the password module. I do not want to have 100+ files with the passwords of the users. I need to have a singe file at the end with all information.
I'm not 100% sure I understood your question.
The below will take your actual user list, create a new one with a generated password for each and store the result in a single file for all users. Bonus: if the file exists, the var will be initialized from there bypassing the password creation.
Notes:
The below can be enhanced. You can put the block tasks for file creation in a separate file and use a conditional include so that the skipped loop iteration do not take place at all when the file already exists.
I obviously did not take security into account here and I strongly suggest you secure the way your file is stored.
Demo playbook:
---
- name: Create random passwords and store
hosts: localhost
gather_facts: false
vars:
users_with_pass_file: /tmp/test_pass.txt
students:
- Username: testuser1
E-Mail: student1#student.com
- Username: testuser2
E-Mail: student2#student.com
tasks:
- name: Try to load users and passwords from file if it exists
vars:
file_content: "{{ lookup('ansible.builtin.file', users_with_pass_file, errors='ignore') }}"
ansible.builtin.set_fact:
users_with_pass: "{{ file_content }}"
when:
- file_content | length > 0
# Unfortunately, there is no test 'is list' in jinja2...
- file_content is iterable
- file_content is not mapping
- file_content is not string
ignore_errors: true
changed_when: false
register: load_from_disk
- name: Create user list with passwords and store it if it does not exists (or is malformed...)
block:
- name: Create the list
vars:
user_password: "{{ lookup('ansible.builtin.password', '/dev/null', length=12) }}"
ansible.builtin.set_fact:
users_with_pass: "{{ users_with_pass | default([]) + [item | combine({'password': user_password})] }}"
loop: "{{ students }}"
- name: Store the result
ansible.builtin.copy:
dest: "{{ users_with_pass_file }}"
content: "{{ users_with_pass | to_json }}"
when: load_from_disk is skipped or load_from_disk is failed
- name: Show the result
ansible.builtin.debug:
var: users_with_pass
first run (with used ansible version):
$ ansible-playbook --version
ansible-playbook 2.10.1
config file = None
configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.6/dist-packages/ansible
executable location = /usr/local/bin/ansible-playbook
python version = 3.6.9 (default, Jul 17 2020, 12:50:27) [GCC 8.4.0]
$ ansible-playbook test.yml
PLAY [Create random passwords and store] ***********************************************************************************************************************************************************************************************
TASK [Try to load users and passwords from file if it exists] **************************************************************************************************************************************************************************
[WARNING]: Unable to find '/tmp/test_pass.txt' in expected paths (use -vvvvv to see paths)
skipping: [localhost]
TASK [Create the list] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'Username': 'testuser1', 'E-Mail': 'student1#student.com'})
ok: [localhost] => (item={'Username': 'testuser2', 'E-Mail': 'student2#student.com'})
TASK [Store the result] ****************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [Show the result] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"users_with_pass": [
{
"E-Mail": "student1#student.com",
"Username": "testuser1",
"password": "5l1RG7HzqaKMWJcH:mRH"
},
{
"E-Mail": "student2#student.com",
"Username": "testuser2",
"password": "tZvLT3LVj3_60GV_WoQd"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Second run:
PLAY [Create random passwords and store] ***********************************************************************************************************************************************************************************************
TASK [Try to load users and passwords from file if it exists] **************************************************************************************************************************************************************************
ok: [localhost]
TASK [Create the list] *****************************************************************************************************************************************************************************************************************
skipping: [localhost] => (item={'Username': 'testuser1', 'E-Mail': 'student1#student.com'})
skipping: [localhost] => (item={'Username': 'testuser2', 'E-Mail': 'student2#student.com'})
TASK [Store the result] ****************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Show the result] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"users_with_pass": [
{
"E-Mail": "student1#student.com",
"Username": "testuser1",
"password": "5l1RG7HzqaKMWJcH:mRH"
},
{
"E-Mail": "student2#student.com",
"Username": "testuser2",
"password": "tZvLT3LVj3_60GV_WoQd"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
If you have diceware installed, then you can generate the password:
- name: generate new password
delegate_to: localhost
command: diceware --no-caps -d ' ' -w en_eff
register: generatedpass
Then you can store in the file:
- name: store password
copy:
dest: '/path/to/password/file'
content: |
{{ generatedpass }}
If I understand your question correctly, this should do the trick.

Check for Value for a Key in Dict within Ansible

I want to run a Ansible Task in a string(vlan) is found within a keys (name) value. i.e
dict
interfaces_l3:
- name: vlan101
ipv4: 192.168.1.100/24
state: present
task
- name: Enable Features
nxos_feature:
feature: interface-vlan
state: enabled
when: vlan in interfaces_l3.values()
This is what I have but currently, this is not working.
There are a few problems with your expression:
interfaces_l3.values() should just blow up, because interfaces_l3 is a list, and lists don't have a .values() method.
You are referring to a variable named vlan rather than a string "vlan".
You are asking if any item in the interfaces_l3 list contains the string "vlan" in the value of the name attribute. You could do something like this:
---
- hosts: localhost
gather_facts: false
vars:
interfaces_l3_with_vlan:
- name: vlan101
ipv4: 192.168.1.100/24
state: present
interfaces_l3_without_vlan:
- name: something else
ipv4: 192.168.1.100/24
state: present
tasks:
- name: this should run
debug:
msg: "enabling features"
when: "interfaces_l3_with_vlan|selectattr('name', 'match', 'vlan')|list"
- name: this should be skipped
debug:
msg: "enabling features"
when: "interfaces_l3_without_vlan|selectattr('name', 'match', 'vlan')|list"
Which produces the following output:
PLAY [localhost] ******************************************************************************************************************************************************************************
TASK [this should run] ************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "enabling features"
}
TASK [this should be skipped] *****************************************************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0

Using lookup plugin to check SHA512 password hash

In my Ansible git repo, I have a var file with contents like this
vault_users:
alex:
password: $6$PwhqORmvn$tXctAkh9RLs60ZFhn9Cxz/eLZEx1UhQkbDIoM6xWsk7M18TApDd9/b8CHJnEiaiQE2YJ8mqu6kvsGuImDt4dy/
danny:
password: $6$PwhqORmvn$tXctAkh9RLs60ZFhn9Cxz/eLZEx1UhQkbDIoM6xWsk7M18TApDd9/b8CHJnEiaiQE2YJ8mqu6kvsGuImDt4dy/
gary:
password: $6$PwhqORmvn$tXctAkh9RLs60ZFhn9Cxz/eLZEx1UhQkbDIoM6xWsk7M18TApDd9/b8CHJnEiaiQE2YJ8mqu6kvsGuImDt4dy/
Now, I want to check if the password hashes from this var file matches the ones from the /etc/shadow file on a remote server. I know it is possible to mix Ansible and a bash/python script to get what I want. I would like to know if it is possible to do this using pure Ansible playbooks only (no bash/python scripts) using the lookup plugin or some other Ansible feature.
You can use line in file to check if line has changed, register result and store it in another variable if lineinfile module returned "changed".
Unfortunately, due to this bug you can't simply use with_items and backrefs in lineinfile module to check if strings are valid, so i used a little include hack.
So we have a playbook called playbook.yml and task called checkpasswords.yml, let's explain each of them.
playbook.yml
- hosts: localhost
tasks:
# execute checkpasswords.yml for each user in vault_users dict
# and pass each user (or item) as {{ user }} variable to included task
- include: checkpasswords.yml user="{{ item }}"
with_items: "{{ vault_users }}"
- debug: msg="{{ changed_users|default([]) }}"
checkpasswords.yml
- name: check for user and hash
lineinfile:
dest: /etc/shadow
regexp: '{{ user }}:([^:]+):(.*)'
# replace sting with user:hashed_password:everything_that_remains
line: '{{ user }}:{{ vault_users[user].password }}:\2'
state: present
backrefs: yes
register: u
- name: changed users
set_fact:
# set changed_users list to [] if not present and add [user] element
# when user password has changed
changed_users: "{{ changed_users|default([]) + [user] }}"
when: u.changed
hashvars.yml
vault_users:
root:
password: "nothing to see here"
my_user:
password: "nothing here"
I included variables to hashvars.yml file and changed hashes for my_user and root inside it. So the result of executing this playbook will be something like output below, don't forget --check!
ansible-playbook playbook.yml -e #hashvars.yml --check
PLAY [localhost] ***************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/my_user/workspace/so/checkpasswords.yml for localhost
included: /home/my_user/workspace/so/checkpasswords.yml for localhost
TASK [check for user and hash] *************************************************
changed: [localhost]
TASK [changed users] ***********************************************************
ok: [localhost]
TASK [check for user and hash] *************************************************
changed: [localhost]
TASK [changed users] ***********************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
"my_user",
"root"
]
}
PLAY RECAP *********************************************************************
localhost : ok=8 changed=2 unreachable=0 failed=0

Ansible - Using user input to chose a variable

I would like to select a specific variable based on user input in an Ansible playbook. Specifically, I would like to ask for user input on a location of a server, and then execute a specific action based on the input.
This is the current ansible playbook:
- hosts: all
remote_user: root
gather_facts: True
vars:
loc1: "10.13.1.140"
loc2: "10.13.1.141"
loc3: "10.13.1.142"
vars_prompt:
- name: location
prompt: "Location of server? Input options: loc1/loc2/loc3"
private: no
tasks:
- name: Test connectivity to user selected location
wait_for: host={{ vars.location }} port=9999 delay=0 timeout=10 state=started
Output when running the playbook:
[root#ansmgtpr-labc01 cfengine]# ansible-playbook testpoo.yaml -i /tmp/test
SSH password:
Location of server? Input options: loc1/loc2/loc3: loc2
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [hostname.domain.com]
TASK [Test connectivity to user selected location] *****************************
fatal: [hostname.domain.com]: FAILED! => {"changed": false, "elapsed": 10, "failed": true, "msg": "Timeout when waiting for loc2:9999"}
PLAY RECAP *********************************************************************
hostname.domain.com : ok=1 changed=0 unreachable=0 failed=1
I would like to know how or the best way to link the read-in user input of the location with the actual value (IP address) of the location that is defined at the top in the variables section. Possibly eval or something else?
Your task is waiting for loc2, hence the message Timeout when waiting for loc2:9999.
Use host={{ vars[location] }} instead.
Compare the output of the following tasks:
tasks:
- name: Show the value user entered
debug: var=vars.location
- name: Use the entered value as an index
debug: var=vars[location]
Result (abbreviated):
TASK [Show the value user entered] *********************************************
ok: [localhost] => {
"vars.location": "loc2"
}
TASK [Use the entered value as an index] ***************************************
ok: [localhost] => {
"vars[location]": "10.13.1.141"
}

Resources