I am trying to create new users and groups using Ansible playbook. Below is my folder structure.
tree
.
├── create-users.yaml
└── ubuntu
create-users.yaml playbook contains create user and group tasks. Note, I am not having any group (admin_group) and users (Rajini, Kamal) in my target machine, instead they will be created when running the playbook.
---
- name: Create Users & Groups
hosts: target1
gather_facts: false
tasks:
- name: Create Users Task
user:
name: "{{ item }}"
state: present
password: "{{ 'default_user_password' | password_hash('sha512','A512') }}"
shell: /bin/bash
groups: "{{ admin_group }}"
loop:
- Rajini
- Kamal
I have another file called ubuntu to pick group name and password. When running the playbook I am getting below error.
ansible-playbook --vault-id #prompt create-users.yaml -K
BECOME password:
Vault password (default):
PLAY [Create Users & Groups] *****************************************************************************************************************************************************************
TASK [Create Users Task] *********************************************************************************************************************************************************************
fatal: [target1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'admin_group' is undefined\n\nThe error appears to be in '/home/osboxes/Ansible_Project/web_deployment/Ansible/groups_vars/create-users.yaml': line 6, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: Create Users Task\n ^ here\n"}
PLAY RECAP ***********************************************************************************************************************************************************************************
target1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
admin_group: admin
default_user_password: Password1
Can somebody please help me on this?
Updating Output after getting help from user Moon.
ansible-playbook --vault-id #prompt create-users.yaml -K
BECOME password:
Vault password (default):
PLAY [Create Users & Groups] *****************************************************************************************************************************************************************
TASK [Create Users Task] *********************************************************************************************************************************************************************
changed: [target1] => (item=Rajini)
changed: [target1] => (item=Kamal)
PLAY RECAP ***********************************************************************************************************************************************************************************
target1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ssh Kamal#192.168.0.1
Kamal#192.168.0.1's password:
Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 5.0.0-23-generic x86_64)
Kamal#Ansible_Target1:~$ id
uid=1005(Kamal) gid=1001(admin) groups=1001(admin)
Couple of things:
To use variables from ubuntu file you need specify the vars file in playbook.
To use default_user_password as a variable, remove the quotes '
If you want admin as the users primary group then use group attribute instead. groups on the other hand takes a list and add the users to the listed groups.
And, if the group isn't created yet on the target machine then first create the group using group module.
Playbook after the above changes.
---
- name: Create Users & Groups
hosts: target1
gather_facts: false
vars_files: ubuntu
tasks:
- name: Create group
group:
name: "{{ admin_group }}"
state: present
- name: Create Users Task
user:
name: "{{ item }}"
state: present
password: "{{ default_user_password | password_hash('sha512','A512') }}"
shell: /bin/bash
group: "{{ admin_group }}"
loop:
- Rajini
- Kamal
This will help you make your playbook more dynamic:
create a secret.yml file for storing user password using below command:
ansible-vault create secret.yml
#sample:
user_password: mypass#155
create userlist.yml to specify the list of user and their department:
vim userlist.yml
#sample:
##user matching job role mentioned in create-user.yml playbook will be created
- users:
- name: "user1"
job: "developer"
- name: "user2"
job: "devops"
- name: "user3"
job: "developer"
Now create your playbook as follows:
vim user-create.yml
- hosts: appserver1
vars_files:
- secret.yml
- userlist.yml
tasks:
- name: "create group"
group:
name: "{{ item }}"
loop:
- "dev"
- "devops"
- name: "create user when Job=developer from userlist.yml file with password from secret.yml file and add to secondary group 'dev' "
user:
name: "{{ item['name'] }}"
password: "{{ user_password | password_hash('sha512') }}"
update_password: on_create
groups: "dev"
append: yes
loop: "{{ users }}"
when: "item['job'] == 'developer'"
- name: "create user when Job=devops from userlist.yml file with password from secret.yml file and add to secondary group 'devops' "
user:
name: "{{ item['name'] }}"
password: "{{ user_password | password_hash('sha512') }}"
update_password: on_create
groups: "devops"
append: yes
loop: "{{ users }}"
when: "item['job'] == 'devops'"
Run the playbook
ansible-playbook -i inventory user-create.yml --ask-vault-pass
Things to remember
If you do not specify the update_password: on_create option, Ansible re-sets the user password every time the playbook is run: if the user has changed the password since the last time the playbook was run, Ansible re-sets password.
Related
The ansible code below takes an ansible vault (vault.yml) and then uses the ansible.builtin.unvault lookup to retrieve and save the entire vault as the variable full_vault. The output of the debug shows the code in json. This code is working as expected.
- name: Pull vault into Variable from encrypted YAML file
hosts: localhost
gather_facts: no
tasks:
- name: Get specific value from vault file
set_fact:
full_vault: "{{ lookup('ansible.builtin.unvault', 'vault.yml') | from_yaml }}"
- name: Display Vault
ansible.builtin.debug:
msg: "Vault: {{ full_vault }}"
The challenge I am having is trying to use the ansible.builtin.vault lookup to put the full_vault variable back into an ansible vault. How can I accomplish this in a single task?
I am using the environment variable ANSIBLE_VAULT_PASSWORD_FILE=pass.txt for encryption/decryption.
Your question and example is focusing on the ansible.builtin.unvault lookup which is abolutely not needed in your situation. The only case I can think of where this would be needed is if you fetch your vault password from an other system/app/source while running your playbook. But since it is available with classic env vars to ansible, you just have to use the encrypted file which will be decrypted on the fly.
For the rest of the example, let's imagine your vault.yml file contains (decrypted):
my_login: vip
my_pass: v3rys3cr3t
some_other_key: toto
Using the above encrypted file is as easy as
---
- hosts: localhost
gather_facts: false
vars_files:
- vault.yml
tasks:
- name: Dummy use of login and pass
ansible.builtin.debug:
msg: "Login in {{ my_login }} and password is {{ my_pass }}"
Now if you want to easily load that file with all its content, change a value for a key in the contained dict and push back the content encrypted with the same configured password, here is a first draft that you will probably have to enhanced. But it worked for my local test with your current configuration.
The update_vault.yml playbook
---
- hosts: localhost
gather_facts: false
vars:
vault_file: vault.yml
new_pass: n3ws3cr3t
tasks:
- name: Import vaulted variables in a namespace (for further easier manipulation)
ansible.builtin.include_vars:
file: "{{ vault_file }}"
name: my_vault
- name: Dummy task just to show above worked
debug:
msg:
- Login is {{ my_vault.my_login }}.
- Password is {{ my_vault.my_pass }}
- Some other key is {{ my_vault.some_other_key }}
- name: Update an element and push back to encrypted file
vars:
new_vault_content: "{{ my_vault | combine({'my_pass': new_pass}) }}"
vault_pass_file: "{{ lookup('ansible.builtin.env', 'ANSIBLE_VAULT_PASSWORD_FILE') }}"
vault_pass: "{{ lookup('ansible.builtin.file', vault_pass_file) }}"
copy:
content: "{{ new_vault_content | to_nice_yaml | ansible.builtin.vault(vault_pass) }}"
dest: "{{ vault_file }}"
decrypt: false
gives:
$ ansible-playbook update_vault.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [Import vaulted variables in a namespace (for furthre easier manipulation)] *******************************************************************************************************************************************************
ok: [localhost]
TASK [Dummy task just to show above worked] ********************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"Login is vip.",
"Password is v3rys3cr3t",
"Some other key is toto"
]
}
TASK [Update an element and push back to encrypted file] *******************************************************************************************************************************************************************************
changed: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
And you can easilly check the file was correctly updated:
$ ansible-vault view vault.yml
my_login: vip
my_pass: n3ws3cr3t
some_other_key: toto
Note that the above playbook is not idempotent. If you run it a second time, the decrypted content of your file will stay identical (with the same new password), but the file will still be changed as the vault salt will change and the encrypted content will be different.
New ansible user
Here is my playbook.
---
- name: Creating Local User Account on RHEL Systems.
hosts: hapansible05
become: true
vars:
passwd: WSXcde1234
tasks:
- name: Creating Local User
user:
name: svc_cldscp
password: "{{ passwd | password_hash('sha512') }}"
comment: svc_cldscp-ServiceAcct
shell: /bin/bash
Keep getting this message on RHEL server
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
PLAY [Creating Local User Account on RHEL Systems.] ********************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************
ok: [hapansible05]
TASK [Creating Local User] ***********************************************************************************************************************************
****fatal: [hapansible05]: FAILED! => {"changed": false, "msg": "usermod: user 'svc_cldscp' does not exist in /etc/passwd\n", "name": "svc_cldscp", "rc": 6}******
PLAY RECAP *************************************************************************************************************************************************
hapansible05 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
A working practice example for creating and configuring local users via user module with a filter for Hashing and encrypting strings and passwords is
---
- name: Create local user account on RHEL System
hosts: rhel.example.com
become: true
vars:
pwd: "WSXcde1234"
tasks:
- name: Create and configure user in local system
user:
name: "svc_cldscp"
password: "{{ pwd | password_hash('sha512') }}"
system: false # Defaults to no
createhome: true # Defaults to yes
uid: '1234' #
group: '1234' # Need to exist before
shell: /bin/bash # Defaults to /bin/bash
comment: "Service Account"
state: present
I am trying to automate ACI with ansible, very new to ansible, as a start point, I started to create simple Tenant and VRF and I am using a variable with my playbook, here it is my playbook like below.
---
- name: Playbook
hosts: APIC
connection: local
gather_facts: no
tasks:
- name: Create new tenant
aci_tenant:
host: "{{ apic_host }}"
username: "{{ apic_username }}"
password: "{{ apic_password }}"
tenant: "{{ tenant_name }}"
validate_certs: False
description: "{{ tenant_description }}"
state: present
# - pause:
# seconds: 10
- name: Add a new VRF
aci_vrf:
host: "{{ apic_host }}"
username: "{{ apic_username }}"
password: "{{ apic_password }}"
tenant: "{{ tenant_name }}"
validate_certs: false
vrf: "{{ vrf_name }}"
policy_control_preference: enforced
policy_control_direction: ingress
state: present
# - pause:
# seconds: 30
and here is my variable file
---
apic_host: sandboxapicdc.cisco.com
apic_username: admin
apic_password: ciscopsdt
tenant_name: zhajili_TN
tenant_description: 'zhajili_first_ansible_tenant'
ap_name: 'AP'
ap_description: 'AP created with Ansible'
vrf_name: 'zhajili_VRF'
When I execute the playbook I receive the following error.
$ ansible-playbook tenants_vrfs.yml -i inventory -v
No config file found; using defaults
PLAY [Playbook] ************************************************************************************************************************************************************************************************
TASK [Create new tenant] ***************************************************************************************************************************************************************************************
fatal: [apicsim_sandbox]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'apic_host' is undefined\n\nThe error appears to be in '/Users/zhajili/Desktop/ansible/ACI/Cisco Sandbox/Tenants_and_VRFs/tasks/tenants_vrfs.yml': line 8, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: Create new tenant\n ^ here\n"}
PLAY RECAP *****************************************************************************************************************************************************************************************************
apicsim_sandbox : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
How declare two prompt variables for hosts in ansible playbook, I tried below Playbook but without luck.
............................................................................................................................................................
Thank you in advance.
---
- name: MD5 File Check
gather_facts: false
hosts: "{{ cluster_host_1 }}", "{{cluster_host_2 }}"
hosts: localhost
remote_user: sv_operator
vars_prompt:
- name: "file_1"
prompt: "File name"
private: no
- name: "cluster_host_1"
prompt: "Enter 1st Host name"
private: no
- name: "cluster_host_2"
prompt: "Enter 2nd Host Name"
private: no
tasks:
- stat:
path: "/tmp/{{ file_1 }}"
checksum_algorithm: sha256
register: output
delegate_to: "{{ cluster_host_1 }}"
- debug:
msg: "{{ output.stat.checksum }}"
- stat:
path: "/tmp/{{ file_2 }}"
checksum_algorithm: sha256
register: output_
delegate_to: "{{ cluster_host_2 }}"
- debug:
msg: "{{ output_.stat.checksum }}"
Given the remote hosts and /tmp/file1
shell> ssh admin#test_01 sha256 /tmp/file1
SHA256 (/tmp/file1) = e2611a1fac7fc2ab99d2e792ad84f34e66740d6a3d77b97b4da39a3758357da0
shell> ssh admin#test_02 sha256 /tmp/file1
SHA256 (/tmp/file1) = 109f60103192b5c8f4e33c26b4f9c7b94489bf8de0325497b7f5a0668dc1a402
The playbook below
shell> cat playbook.yml
- name: SHA256 File Check
hosts: localhost
gather_facts: false
vars_prompt:
- name: "file_1"
prompt: "File name"
private: no
- name: "cluster_host_1"
prompt: "Enter 1st Host name"
private: no
- name: "cluster_host_2"
prompt: "Enter 2nd Host Name"
private: no
tasks:
- stat:
path: "/tmp/{{ file_1 }}"
checksum_algorithm: sha256
register: output
delegate_to: "{{ cluster_host_1 }}"
- debug:
msg: "{{ output.stat.checksum }}"
- stat:
path: "/tmp/{{ file_1 }}"
checksum_algorithm: sha256
register: output_
delegate_to: "{{ cluster_host_2 }}"
- debug:
msg: "{{ output_.stat.checksum }}"
works as expected
shell> ansible-playbook playbook.yml
File name: file1
Enter 1st Host name: test_01
Enter 2nd Host Name: test_02
PLAY [SHA256 File Check] ****
TASK [stat] ****
ok: [localhost -> test_01]
TASK [debug] ****
ok: [localhost] =>
msg: e2611a1fac7fc2ab99d2e792ad84f34e66740d6a3d77b97b4da39a3758357da0
TASK [stat] ****
ok: [localhost -> test_02]
TASK [debug] ****
ok: [localhost] =>
msg: 109f60103192b5c8f4e33c26b4f9c7b94489bf8de0325497b7f5a0668dc1a402
PLAY RECAP ****
localhost: ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
If you want your play to run on two hosts, declared through variables. You have two options.
Option 1:
Since your target hosts are comma separated, you might as well input them that way.
- hosts: '{{ my_hosts }}'
gather_facts: false
remote_user: 'sv_operator'
vars_prompt:
- name: 'my_hosts'
prompt: 'Comma separated list of hosts'
private: no
Then when prompted:
Comma separated list of hosts: host1.local,host2.local
Option 2:
Capture each host in individual variable like host_1 and host_2, then call it as a list in a play.
- hosts:
- '{{ host_1 }}'
- '{{ host_2 }}'
gather_facts: false
remote_user: 'sv_operator'
vars_prompt:
- name: 'host_1'
prompt: 'First host'
private: no
- name: 'host_2'
prompt: 'Second host'
private: no
Then when prompted:
First host: host1.local
Second host: host2.local
I would like to generate a password and some other values when they do not exist yet.
Something like this:
- name: Retrieve or generate my_password
generated_fact:
shell: some shell command
name: my_password
I have a working approach but am really unhappy with it since it is very verbose and error prone:
- name: Generate my_password
shell: some shell command
register: generate_password_task
when: ansible_local.convoluted.bs.my_password is not defined
- name: Store my_password as local fact
ini_file:
path: "/etc/ansible/facts.d/convoluted.fact"
section: bs
option: my_password
value: "{{ generate_password_task.stdout }}"
when: generate_password_task.changed
- name: Reload Ansible local facts
setup: filter=ansible_local
when: generate_password_task.changed
Is there a high level abstraction for this task along the lines of the first code snippet? Or some other approach more sane than what I currently have?
The basic approach is the right one. There are few imperfections I see. One of them is 'dependence chain', when different tasks are depend on different conditions. It's hard to debug.
So, improvement one is to make a single condition:
- block:
when: ansible_local.convoluted.bs.my_password is not defined
- name: Generate my_password
shell: some shell command
register: generate_password_task
- name: Store my_password as local fact
ini_file:
path: "/etc/ansible/facts.d/convoluted.fact"
section: bs
option: my_password
value: "{{ generate_password_task.stdout }}"
- name: Reload Ansible local facts
setup: filter=ansible_local
The second trick is to use meta: end_host, it allows to terminate play for the specific host without errors and additional skips.
- hosts: ...
tasks:
- meta: end_play
when: ansible_local.convoluted.bs.my_password is defined
- name: Generate my_password
shell: some shell command
register: generate_password_task
- name: Store my_password as local fact
ini_file:
path: "/etc/ansible/facts.d/convoluted.fact"
section: bs
option: my_password
value: "{{ generate_password_task.stdout }}"
- name: Reload Ansible local facts
setup: filter=ansible_local
But you need to keep it as a separate play to use it with 'end_host'.
Here would be my approach, in two tasks.
Note that this has been checked when:
The ini file does not exists
The ini file is present but there is no bs section
The ini file is present, have a bs section, but the section does not include a my_password option
The ini file is present, have a bs section, but the option my_password is empty
The ini file is present, have a bs section and have a value for the option my_password
The cases 1 to 4 does end in a change of the ini file with a newly generated password, when the last case ends in a skipped task.
And here are the two tasks doing this:
- set_fact:
actual_password: "{{ lookup('ini', 'my_password section=bs file=/etc/ansible/facts.d/convoluted.fact', errors='ignore') }}"
- ini_file:
path: /etc/ansible/facts.d/convoluted.fact
section: bs
option: my_password
value: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation') }}"
when: actual_password|length == 0
Those are using
the ini lookup in order to confirm that the ini and value are not yet set
the password plugin to generate a password
The condition (when: actual_password|length == 0) to write the ini is based on experimentation
Thanks to the errors='ignore' syntax in the ini lookup, the variable actual_password ends up being an empty string if the file or section does not exists, because both those cases are causing the lookup to return an error
If the section exists but does not contains the option then the lookup ends up returning an empty array []
If the option is there but empty, the lookup return that empty string
Knowing all that is making the test relevant: []|length == 0 and ''|length == 0 are both true.
A last note on the password plugin, yes it does write a file on your controller host, but you are not forced to consider it, use it, or even, well, store it.
The plugin return the password, so you can easily use it as you would for any other variable in the value attribute of your ini_file module.
And then, if you don't want to store it on the controller host, do as you would do for anything you don't care about in linux, redirect it to /dev/null, either way, the password is in your ini file now.
And if you need to re-use that same password later in the playbook, just store it via an extra set_fact
- set_fact:
actual_password: "{{ lookup('ini', 'my_password section=bs file=/etc/ansible/facts.d/convoluted.fact', errors='ignore') }}"
- block:
- set_fact:
new_password: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation') }}"
- ini_file:
path: /etc/ansible/facts.d/convoluted.fact
section: bs
option: my_password
value: "{{ new_password }}"
when: actual_password|length == 0
Given this playbook:
- hosts: local
gather_facts: no
tasks:
- set_fact:
actual_password: "{{ lookup('ini', 'my_password section=bs file=/etc/ansible/facts.d/convoluted.fact', errors='ignore') }}"
- ini_file:
path: /etc/ansible/facts.d/convoluted.fact
section: bs
option: my_password
value: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation') }}"
when: actual_password|length == 0
Here is a double run of it:
/ansible # cat /etc/ansible/facts.d/convoluted.fact
cat: can't open '/etc/ansible/facts.d/convoluted.fact': No such file or directory
/ansible # ansible-playbook play.yml
PLAY [local] ***********************************************************************************************************************************************************************************************
TASK [set_fact] ********************************************************************************************************************************************************************************************
[WARNING]: Unable to find '/etc/ansible/facts.d/convoluted.fact' in expected paths (use -vvvvv to see paths)
ok: [local]
TASK [ini_file] ********************************************************************************************************************************************************************************************
changed: [local]
PLAY RECAP *************************************************************************************************************************************************************************************************
local : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
/ansible # cat /etc/ansible/facts.d/convoluted.fact
[bs]
my_password = fnI;L3FpR5207,8,jxGP
/ansible # ansible-playbook play.yml
PLAY [local] ***********************************************************************************************************************************************************************************************
TASK [set_fact] ********************************************************************************************************************************************************************************************
ok: [local]
TASK [ini_file] ********************************************************************************************************************************************************************************************
skipping: [local]
PLAY RECAP *************************************************************************************************************************************************************************************************
local : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0