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
Related
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.
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
I'm trying to find the UUID of a VM based on it's hostname. I'm not sure what is missing in my syntax here, but I've tried several different methods. Here is the sample playbook I'm working with currently:
---
- name: Test taking snapshot by UUID
hosts: tst000.company.com
vars_files:
- vars/credentials.yml
tasks:
- name: Gather all registered virtual machines
vmware_vm_facts:
hostname: 'vcenter.company.com'
username: '{{ vcenter.username }}'
password: '{{ vcenter.password }}'
validate_certs: False
delegate_to: localhost
register: vmfacts
- debug:
var: vmfacts.virtual_machines.{{ ansible_facts['hostname'] }}.uuid
- set_fact:
vm_uuid: "{{ lookup('vars', 'vmfacts.virtual_machines.' + ansible_facts['hostname'] + '.uuid') }}"
Results are as follows:
Identity added: /opt/tmp/awx_2507_ithHYD/credential_3
(/opt/tmp/awx_2507_ithHYD/credential_3) Vault password:
PLAY [Test taking snapshot by UUID]
********************************************
TASK [Gathering Facts]
********************************************************* ok: [tst000.company.com]
TASK [Gather all registered virtual machines]
********************************** ok: [tst000.company.com -> localhost]
TASK [debug]
******************************************************************* ok: [tst000.company.com] => {
"vmfacts.virtual_machines.tst000.uuid": "421d2491-8896-e52f-e4f5-5118687ce0e9" }
TASK [set_fact]
**************************************************************** fatal: [tst000.company.com]: FAILED! => {"msg": "The task
includes an option with an undefined variable. The error was: No
variable found with this name:
vmfacts.virtual_machines.tst000.uuid\\n\\nThe error appears to
have been in '/var/lib/awx/projects/quick-stuff/test_snapshot.yml':
line 21, column 7, but may\\nbe elsewhere in the file depending on the
exact syntax problem.\\n\\nThe offending line appears to be:\\n\\n\\n
- set_fact:\\n ^ here\\n"}
PLAY RECAP
********************************************************************* tst000.company.com : ok=3 changed=0 unreachable=0
failed=1
In the debug and set_fact modules, you can see that the ansible_facts['hostname]' properly places the hostname as needed, however, it returns the proper value in the debug module, but is claiming there is no variable found by that name in the set_fact module. I'm not sure what is wrong with my syntax here.
There's no need to use the lookup in that way, since vars and its hostvars friend are effectively dicts:
- set_fact:
vm_uuid: "{{ vmfacts.virtual_machines[ansible_facts['hostname']].uuid }}"
I have a nested ansible playbook (master) file and I want to call included playbook (slave) with their own JSON vars.
Master.yaml
- name: this is a play at the top level of a file
hosts: local
connection: local
tasks:
- debug: msg=hello
- include: slave_first.yaml
- include: slave_second.yaml
slave_first.yaml should make use of "vars/slave_first_vars.json" file and slave_second.yaml should make use of "vars/slave_second_vars.json" file.
When including playbooks you can only override variables with vars statement, like:
- include: slave_first.yaml
vars:
myvar: foo
- include: slave_second.yaml
vars:
myvar: foo
There are no other options for PlaybookInclude.
If you need to load variables from files, you have to use vars_files or include_vars inside your slave playbooks.
In your scenario, I'll use like this, master.yml:
- hosts: localhost
connection: local
tasks:
- include: slave_first.yml
vars:
VAR_FILE: "slave_first_vars"
- include: slave_second.yml
vars:
VAR_FILE: "slave_second_vars"
While slave_first.yml and slave_second.yml are like this, in my case both are same but you get an idea that how you can use them:
slave_first.yml:
---
- include_vars: "{{ VAR_FILE }}.yml"
- debug:
msg: "{{ DOMAIN_NAME }}"
slave_second.yml:
---
- include_vars: "{{ VAR_FILE }}.yml"
- debug:
msg: "{{ DOMAIN_NAME }}"
Now come to the different variable part:
slave_first_vars.yml: in your case it will be json
---
DOMAIN_NAME: "first.com"
slave_second_vars.yml:
---
DOMAIN_NAME: "second.com"
Then you can run and verify that if work as expected:
➤ansible-playbook -i localhost, master.yml
PLAY [localhost] **********************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [localhost]
TASK [include_vars] **********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "first.com"
}
TASK [include_vars] **********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "second.com"
}
PLAY RECAP **********************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
Hope that might help you!
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"
}