How to generate single reusable random password with ansible - ansible

That is to say: How to evaluate the password lookup only once?
- name: Demo
hosts: localhost
gather_facts: False
vars:
my_pass: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters') }}"
tasks:
- debug:
msg: "{{ my_pass }}"
- debug:
msg: "{{ my_pass }}"
- debug:
msg: "{{ my_pass }}"
each debug statement will print out a different value, e.g:
PLAY [Demo] *************
TASK [debug] ************
ok: [localhost] => {
"msg": "ZfyzacMsqZaYqwW"
}
TASK [debug] ************
ok: [localhost] => {
"msg": "mKcfRedImqxgXnE"
}
TASK [debug] ************
ok: [localhost] => {
"msg": "POpqMQoJWTiDpEW"
}
PLAY RECAP ************
localhost : ok=3 changed=0 unreachable=0 failed=0
ansible 2.3.2.0

Use set_fact to assign permanent fact:
- name: Demo
hosts: localhost
gather_facts: False
vars:
pwd_alias: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters') }}"
tasks:
- set_fact:
my_pass: "{{ pwd_alias }}"
- debug:
msg: "{{ my_pass }}"
- debug:
msg: "{{ my_pass }}"
- debug:
msg: "{{ my_pass }}"

I've been doing it this way and never had an issue.
- name: Demo
hosts: localhost
gather_facts: False
tasks:
- set_fact:
my_pass: "{{ lookup('password', '/dev/null length=15 chars=ascii_letters') }}"
- debug:
msg: "{{ my_pass }}"

The lookup password is nice, but what if you have password specification, like it have to contain specific characters, or must not contain uppercase... the lookup also does not guarantee that the password will have special characters if needed to have...
I have ended with custom jinja filter, that might help somebody ( works fine for me :) )
https://gitlab.privatecloud.sk/vladoportos/custom-jinja-filters

The problem is that you are using the password module wrong, or at least according to the latest documentation (maybe this a new feature on 2.5):
Generates a random plaintext password and stores it in a file at a given filepath.
By definition,the lookup password generates a random password AND stores it on the specified path for subsequent lookups. So, first time it checks if the specified path exists, and if not generates a random password and stores it on that path, subsequent lookups will just retrieve it. Because you are using /dev/null as store path, you are forcing ansible to generate a new random password because everytime it checks for existence it finds nothing.
If you want to have a random password per host + client or whatever
all you need to do to is use some templating and set the store path based on those parameters.
For example:
---
- name: Password test
connection: local
hosts: localhost
tasks:
- name: create a mysql user with a random password
ansible.builtin.debug:
msg: "{{ lookup('password', 'credentials/' + item.host + '/' + item.user + '/mysqlpassword length=15') }}"
with_items:
- user: joe
host: atlanta
- user: jim
host: london
- name: Another task that uses the password of joe
ansible.builtin.debug:
msg: "{{ lookup('password', 'credentials/atlanta/joe/mysqlpassword length=15') }}"
- name: Another task that uses the password of jim
ansible.builtin.debug:
msg: "{{ lookup('password', 'credentials/london/jim/mysqlpassword length=15') }}"
And this is the task execution, as you can see, the three tasks are getting the right generated passwords:
TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]
TASK [create a mysql user with a random password] ********************************************************************
ok: [localhost] => (item={'user': 'joe', 'host': 'atlanta'}) => {
"msg": "niwPf4tk9HWHhNc"
}
ok: [localhost] => (item={'user': 'jim', 'host': 'london'}) => {
"msg": "dHJdg,OjOEqdyrW"
}
TASK [Another task that uses the password of joe] ********************************************************************
ok: [localhost] => {
"msg": "niwPf4tk9HWHhNc"
}
TASK [Another task that uses the password of jim] ********************************************************************
ok: [localhost] => {
"msg": "dHJdg,OjOEqdyrW"
}
This has the advantage that, even if you play fails and you have to re-execute you will not get the same previous random password,that you can then store on a key-chain or just delete them.

Related

Loop over group IP's

I have a group with hosts and to probe those with GlusterFS I need to turn the group into a list of IP's.
I have searched here and on Google and tried a lot of things to get this done, some include lots of code and others templates and regex and what not.
Currently I have not found a single solution to just list the IP's of a group of hosts.
This is what I made myself, it takes the group, turns this dict into a list and then loops over the list, great! But when I loop over it the results are strings (why?!) and I cannot get the IP's anymore.
- name: workit
vars:
main_nodes_ips: "{{ groups['glusterpeers'] | list }}"
loop: "{{ main_nodes_ips }}"
debug:
msg: "{{ item['hostvars'] }}"
I found this
- debug: var=hostvars[inventory_hostname]['ansible_default_ipv4']['address']
But how to turn it into this (example not working)
- debug: var=groups['glusterpeers']['ansible_default_ipv4']['address']
I do not understand why a list of hosts turns into a list of strings all of a sudden, but maybe I should not think of it as objects.
My Result
This is what I ended up with to probe all the peers for the GlusterFS. In the 'when' blocks I hard code the hostname of the first node, not great. I still wonder if there is not a more elegant way of probing the peers, it must be something all GlusterFS users do.
Als taking the second IP may give trouble when this order of IP's changes so hostname is probably better.
- hosts: all
gather_facts: true
tasks:
- set_fact:
main_nodes_ips: "{{ groups.zicluster|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map(attribute='1')|
list }}"
run_once: true
- debug:
var: main_nodes_ips[1:]
when: inventory_hostname == 'zi01'
- name: probe the peers
gluster.gluster.gluster_peer:
state: present
nodes: "{{ main_nodes_ips[1:] }}"
when: inventory_hostname == 'zi01'
For example, the playbook below takes the first IP address of a remote host and creates the list
- hosts: glusterpeers
gather_facts: true
tasks:
- debug:
var: ansible_all_ipv4_addresses
- set_fact:
main_nodes_ips: "{{ groups.glusterpeers|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map('first')|
list }}"
run_once: true
- debug:
var: main_nodes_ips
run_once: true
gives
PLAY [glusterpeers] ***************************************
TASK [Gathering Facts] ************************************
ok: [host02]
ok: [host01]
ok: [host03]
TASK [debug] **********************************************
ok: [host01] =>
ansible_all_ipv4_addresses:
- 10.1.0.61
ok: [host02] =>
ansible_all_ipv4_addresses:
- 10.1.0.62
ok: [host03] =>
ansible_all_ipv4_addresses:
- 10.1.0.63
TASK [set_fact] *******************************************
ok: [host01]
TASK [debug] **********************************************
ok: [host01] =>
main_nodes_ips:
- 10.1.0.61
- 10.1.0.62
- 10.1.0.63
Notes
If you want to take any other index from the list map attribute. For example, to get the 2nd item, replace
map('first')|
by
map(attribute='1')|
In your code, you remove the first IP from the list for a particular host. This can work for the first host only
- debug:
msg: "{{ main_nodes_ips[1:] }}"
when: inventory_hostname == 'host01'
Create a dictionary if you want to remove the IP for any host. e.g.
- set_fact:
main_nodes: "{{ dict(groups.glusterpeers|zip(main_nodes_ips)) }}"
run_once: true
gives
main_nodes:
host01: 10.1.0.61
host02: 10.1.0.62
host03: 10.1.0.63
Then, use this dictionary to remove the IP of the particular host from the list
- debug:
msg: "{{ main_nodes_ips|difference(main_nodes[inventory_hostname]) }}"
when: inventory_hostname == 'host01'
gives
TASK [debug] **************************************************
skipping: [host02]
skipping: [host03]
ok: [host01] =>
msg:
- 10.1.0.62
- 10.1.0.63

Use dynamic variable name

I'm trying to get the value of ip_address from the following yaml that I'm including as variables on ansible:
common:
ntp:
- time.google.com
node1:
default_route: 10.128.0.1
dns:
- 10.128.0.2
hostname: ip-10-128-5-17
device_interface: ens5
cluster_interface: ens5
interfaces:
ens5:
ip_address: 10.128.5.17
nat_ip_address: 18.221.63.178
netmask: 255.255.240.0
version: 2
However the network interface (ens5 here) may be named something else, such as eth0. My ansible code is this:
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
vars:
msg: node1.interfaces.{{ cvp_device_interface }}.ip_address
debug:
msg: "{{ msg }}"
tags: debug_info
Running the code like this I can get the key's name:
TASK [Dump Interface Settings] *************************************************
│ ok: [18.221.63.178] => {
│ "msg": "node1.interfaces.ens5.ip_address"
│ }
But what I actually need is the value (i.e: something like {{ vars[msg] }}, which should expand into {{ node1.interfaces.ens5.ip_address }}). How can I accomplish this?
Use sqare brackets.
Example: a minimal playbook, which defines a variable called "device". This variable is used to return the active status of the device.
- hosts: localhost
connection: local
vars:
device: enx0050b60c19af
tasks:
- debug: var=device
- debug: var=hostvars.localhost.ansible_facts[device].active
Output:
$ ansible-playbook example.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************
TASK [Gathering Facts] *************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************
ok: [localhost] => {
"device": "enx0050b60c19af"
}
TASK [debug] ***********************************************************************
ok: [localhost] => {
"hostvars.localhost.ansible_facts[device].active": true
}
PLAY RECAP *************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
see comment
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
debug:
msg: "{{ node1['interfaces'][cvp_device_interface]['ip_address'] }}"
debug:
msg: "{{ msg }}"
tags: debug_info

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.

How to select mandatory character when generating random password with ansible?

I create a random password with Ansible. 4 characters in length.
- hosts: localhost
vars:
pwd_alias: "{{ lookup('password', '/dev/null length=4 chars=ascii_letters,digits,hexdigits,punctuation' ) }}"
user: root
tasks:
- debug:
msg: Sifre= {{pwd_alias}}
- debug:
msg: Sifre= {{pwd_alias}}
- debug:
msg: Sifre= {{pwd_alias}}
- debug:
msg: Sifre= {{pwd_alias}}
I want it to be password example. I want the output to look like this example.
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= Z/bO"
}
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= a_4G"
}
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= 9a&0"
}
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= d.2C"
}
ascii_letters = 1
hexdigits = 1
digits = 1
punctuation = 1
I want him to generate a random password like this. But what the system produces sometimes changes. Sometimes there are no digits, sometimes there is no punctuation. I want these 4 features to be absolutely.

ansible version = 2.7.10
This is how the outputs are
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= Z/bh"
}
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= a_-G"
}
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= 9ad0"
}
TASK [debug]
ok: [localhost] => {
"msg": "Sifre= d.aC"
}
How do I get each character? Thank you so much
Generate the passwords in a separate file. Get random character from each set and create pwd_alias_list. Then shuffle and join the list.
$ cat generate-password-4.yml
- set_fact:
pwd_alias_list: []
- set_fact:
pwd_alias_list: "{{ pwd_alias_list + [
lookup('password', '/dev/null length=1 chars=' ~ item) ]
}}"
loop:
- ascii_letters
- digits
- hexdigits
- punctuation
- set_fact:
pwd_alias: "{{ pwd_alias_list|shuffle|join('') }}"
The tasks below
tasks:
- include_tasks: generate-password-4.yml
- debug:
var: pwd_alias
- include_tasks: generate-password-4.yml
- debug:
var: pwd_alias
- include_tasks: generate-password-4.yml
- debug:
var: pwd_alias
- include_tasks: generate-password-4.yml
- debug:
var: pwd_alias
give
"pwd_alias": "ld(9"
"pwd_alias": "2R`9"
"pwd_alias": "O5(0"
"pwd_alias": "2>z5"
It's possible to make the generation of the password more flexible and create a list of the characters' sets my_char_specs and number of the repetitions my_repeat
$ cat generate-password.yml
- set_fact:
pwd_alias_list: []
- set_fact:
pwd_alias_list: "{{ pwd_alias_list + [
lookup('password', '/dev/null length=1 chars=' ~ item.0) ]
}}"
with_nested:
- "{{ my_char_specs }}"
- "{{ range(0, my_repeat)|list }}"
- set_fact:
pwd_alias: "{{ pwd_alias_list|shuffle|join('') }}"
The task below repeat the random choice from four sets four times
vars:
my_char_specs:
- ascii_letters
- digits
- hexdigits
- punctuation
my_repeat: 4
tasks:
- include_tasks: generate-password.yml
- debug:
var: pwd_alias
and gives
"pwd_alias": "8=3[9BD(7?3bJ5y3"
This solution works, you need to generate each type chars separately then concatenate them :
- hosts: localhost
vars:
pwd_alias_digit1: "{{ lookup('password', '/dev/null length=1 chars=ascii_letters' ) }}"
pwd_alias_digit2: "{{ lookup('password', '/dev/null length=1 chars=digits' ) }}"
pwd_alias_digit3: "{{ lookup('password', '/dev/null length=1 chars=hexdigits' ) }}"
pwd_alias_digit4: "{{ lookup('password', '/dev/null length=1 chars=punctuation' ) }}"
pwd_alias: "{{ pwd_alias_digit1 + pwd_alias_digit2 + pwd_alias_digit3 + pwd_alias_digit4 }}"
user: root
tasks:
- debug:
msg: Sifre= {{pwd_alias}}
- debug:
msg: Sifre= {{pwd_alias}}
- debug:
msg: Sifre= {{pwd_alias}}
- debug:
msg: Sifre= {{pwd_alias}}
Another way is to create your own password lookup plugin : my_password. It's easier to create a new plugin and use it simple in a playbook. It's better and the playbook will remain readable.

Ansible : error in calling the groups through restapi

I am trying fetch to distinct groups in the inventory through a variable.this is the command am trying to run in the playbook to add hosts to the Nagios XI. Am trying to do this using Rest API through CURL command. Am getting error as Incorrect pattern. Can some one please advise about the issue. or help me out with how we can call two groups from the inventory in the same command.
- name: add host to nagios XI.
shell: curl -XPOST "http://16.231.22.60/nagiosxi/api/v1/config/host?apikey=qfOQpKFORCNo7HPunDUsSjW7f2rNNmrdVv3kvYpmQcNdSS2grV2jeXKsgbv3QgfL&pretty=1" -d "host_name={{ item.hostname }}&address={{ item.address }}&use=xiwizard_ncpa_host&max_check_attempts=5&check_period=xi_timeperiod_24x7&notification_interval=60&notification_period=xi_timeperiod_24x7&notifications_enabled=0&contacts=nagiosadmin&contact_groups=Candle Admins,Candle-L1-L2-Internal&applyconfig=1"
with_items:
- { hostname: "{{ groups['grp1'] }}", address: "{{ groups['grp2'] }}"}
EDIT: code formatting
Understanding that your hostname and address from each group match each other you can do the following:
Inventory:
[grp1]
host1
host2
host3
[grp2]
10.100.10.1
10.100.10.2
10.100.10.3
Play:
---
- name: Debug Together
hosts: localhost
gather_facts: False
tasks:
- name: Add host to nagios XI
shell: shell: curl -XPOST "http://16.231.22.60/nagiosxi/api/v1/config/host?apikey=qfOQpKFORCNo7HPunDUsSjW7f2rNNmrdVv3kvYpmQcNdSS2grV2jeXKsgbv3QgfL&pretty=1" -d "host_name={{ item.0 }}&address={{ item.1 }}&use=xiwizard_ncpa_host&max_check_attempts=5&check_period=xi_timeperiod_24x7&notification_interval=60&notification_period=xi_timeperiod_24x7&notifications_enabled=0&contacts=nagiosadmin&contact_groups=Candle Admins,Candle-L1-L2-Internal&applyconfig=1"
with_together:
- "{{ groups['grp1'] }}"
- "{{ groups['grp2'] }}"
You will get something like:
TASK [debug] ******************************************************************************************************************
ok: [localhost] => (item=None) => {
"item.0, item.1": "(u'host1', u'10.100.10.1')"
}
ok: [localhost] => (item=None) => {
"item.0, item.1": "(u'host2', u'10.100.10.2')"
}
ok: [localhost] => (item=None) => {
"item.0, item.1": "(u'host3', u'10.100.10.3')"
}
Comming from my test:
- name:
debug:
var: item.0, item.1
with_together:
- "{{ groups['grp1'] }}"
- "{{ groups['grp2'] }}"

Resources