Ansible with subelements referencing a dict - ansible

Bear with me, please. I've never had to do something this complex with Ansible and I'm really struggling to piece it together.
To sum it up, I already have a dict and a task to deploy our employee's SSH accounts and public keys to our servers. I would like to re-use this dict to also deploy certain employee keys to certain website user accounts. An example probably explains better than I can.
employee_ssh_users:
user1: 'user1key'
user2: 'user2key'
user3: 'user3key'
user4: 'user4key'
- name: Add employee SSH users
user:
name: "{{ item.key }}"
state: present
with_dict: "{{ employee_ssh_users }}"
- name: Add employee public keys to employee accounts
authorized_key:
user: "{{ item.key }}"
state: present
key: "{{ item.value }}"
with_dict: "{{ employee_ssh_users }}"
The above configuration and tasks work fine for adding our employees and their keys to the servers. Now, I want to re-use these keys so that I can add certain employees to certain other users without having to copy and paste the employee's keys. Here is what I'm trying to do:
website_keys:
- name: site1
authorized:
- user1
- user3
- name: site2
authorized:
- user1
- user2
- name: Add employee public keys to website accounts
authorized_key:
user: "{{ item.0.name }}"
key: "{{ hostvars[inventory_hostname]['employee_ssh_users'][' + item.1 '] }}"
with_subelements:
- "{{ website_keys }}"
- authorized
Basically, I can't figure out exactly what I need to do to interpolate the subelement into the key variable, if it's even possible at all.

It's quite simple:
- name: Add employee public keys to website accounts
authorized_key:
user: "{{ item.0.name }}"
key: "{{ employee_ssh_users[item.1] }}"
with_subelements:
- "{{ website_keys }}"
- authorized
You can query employee_ssh_users by name and use item.1 without quotes, as it is a variable itself.

Related

ansible Iterate over list in list

I'm trying to adopt the task in the last answer from this post Ansible - managing multiple SSH keys for multiple users & roles
My variable looks like:
provisioning_user:
- name: ansible
state: present
ssh_public_keyfiles:
- ansible.pub
- user.pub
- name: foo
state: present
ssh_public_keyfiles:
- bar.pub
- key.pub
and my code looks like
- name: lookup ssh pubkeys from keyfiles and create ssh_pubkeys_list
set_fact:
ssh_pubkeys_list: "{{ lookup('file', item.ssh_public_keyfiles) }}"
loop: "{{ provisioning_user }}"
register: ssh_pubkeys_results_list
I want to store keys under the files directory and assign them with the variable to different users so that when a key changes, i only have to change the file and run the playbook instead of changing it in any hostvars file where the old key is used. But I get the following error and dont know how to solve it. I want to do this for every user defined in provisioning_user
fatal: [cloud.xxx.xxx]: FAILED! =>
msg: 'An unhandled exception occurred while running the lookup plugin ''file''. Error was a <class ''AttributeError''>, original message: ''list'' object has no attribute ''startswith''. ''list'' object has no attribute ''startswith'''
Can anyone please help me?
The file lookup reads the content of a single file, but you're passing it a list. That's not going to work, and is the direct cause of the error message you've reported.
You're also using both set_fact and register in the same task, which doesn't make much sense: the whole point of a set_fact task is to create a new variable; you shouldn't need to register the result.
Your life is going to be complicated by the fact that each user can have multiple key files. We need to build a data structure that maps each user name to a list of keys; we can do that like this:
- name: lookup ssh pubkeys from keyfiles
set_fact:
pubkeys: >-
{{
pubkeys |
combine({
item.0.name: pubkeys.get(item.0.name, []) + [lookup('file', item.1)]})
}}
vars:
pubkeys: {}
loop: "{{ provisioning_user|subelements('ssh_public_keyfiles') }}"
This creates the variable pubkeys, which is a dictionary that maps usernames to keys. Assuming that our provisioning_user variable looks like this:
provisioning_user:
- name: ansible
state: present
ssh_public_keyfiles:
- ansible.pub
- user.pub
- name: foo
state: present
ssh_public_keyfiles:
- bar.pub
- key.pub
- name: bar
state: present
ssh_public_keyfiles: []
After running the above task, pubkeys looks like:
"pubkeys": {
"ansible": [
"ssh-rsa ...",
"ssh-rsa ..."
],
"foo": [
"ssh-rsa ...",
"ssh-rsa ..."
]
}
We can use pubkeys in our authorized_keys task like this:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(pubkeys[item.name]) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.name in pubkeys
I think your life would be easier if you were to rethink how you're managing keys. Instead of allowing each user to have a list of multiple key files, have a single public key file for each user -- named after the username -- that may contain multiple public keys.
That reduces your provisioning_user data to:
provisioning_user:
- name: ansible
state: present
- name: foo
state: present
- name: bar
state: present
And in our files/ directory, we have:
files
├── ansible.keys
└── foo.keys
You no longer need the set_fact task at all, and the authorized_keys task looks like:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ keys }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
when: >-
('files/%s.keys' % item.name) is exists
vars:
keys: "{{ lookup('file', '%s.keys' % item.name) }}"
loop: "{{ provisioning_user }}"
Note that in the above the when condition requires an explicit path, while the file lookup will implicitly look in the files directory.
These changes dramatically simplify your playbook.

Ansible can't loop through subelements in variables files

I have the following user lists in separated files.
The idea behind this is to create multiple users and assign them to different user groups.
To make it easier, I shortened the list. I reality they include passwords and etc.
First variables file
userlist-os:
group: os
users:
- comment: Test User
username: ostest1
user_id: 9404
user_state: present
- comment: Test User
username: ostest2
user_id: 9405
user_state: present
Second variables file
userlist-zos:
group: zos
users:
- comment: Test User1
username: zostest1
user_id: 9204
user_state: present
- comment: Test User2
username: zostest2
user_id: 9205
user_state: present
This is how my playbook looks like:
- name: test
hosts: all
user: root
vars_files:
- [userlist-zos.yml]
- [userlist-os.yml]
tasks:
- name: Create user accounts
user:
name: "{{ item.users.username }}"
update_password: on_create
uid: "{{ item.users.user_id }}"
shell: /bin/bash
create_home: yes
group: "{{ item.group }}"
state: present
comment: "{{ item.users.comment }}"
when: item.users.user_state == 'present'
with_items:
- "{{ userlist-os }}"
- "{{ userlist-zos }}"
The problem is that I'm not getting into the sub elements of users(variable username is undefined), but when I set an index like this name: "{{ item.users.0.username }}" I do get the first username from each file.
Any help is appreciated.
In your scenario, item.users are lists of users, they are not dictionaries. Therefore they don't have username field, they have list elements which have that field instead. You were able to access to first element of the list with "item.users.0.username". What I suggest you to do is to access these nested variables with an include_task variable as follows:
main.yaml
- name: Trial
hosts: localhost
vars:
# YOUR VARS
tasks:
- name: Create user accounts
include_tasks: helper.yml
with_items:
- "{{ userlistos }}"
- "{{ userlistzos }}"
loop_control:
loop_var: list
helper.yml
- name: Create user accounts
user:
name: "{{ item.username }}"
update_password: on_create
uid: "{{ item.user_id }}"
shell: /bin/bash
create_home: yes
group: "{{ list.group }}"
state: present
comment: "{{ item.comment }}"
when: item.user_state == 'present'
with_items:
- "{{list.users}}"

Assign multiple public ssh keys to user definitions with authorized_key module in Ansible

Question:
Using Ansible, how can I set multiple public-key files (.pub) to be deployed in a user's authorized_keys file instead of using a single file containing a list of public ssh-keys?
Scenario and requirements:
I have multiple public ssh-keys stored as .pub files on a central location
I want to create new users from a vars file
each user shall have (none/one specific/multiple) public ssh-keys from the selection of .pub files deployed to their respective authorized_keys file
the list of deployed .pub files can change due to:
content of .pub file has changed due to eg. compromised private key
user in vars file shall have additional public key files assigned
I don't want to go through a "list" file for each user to replace a key
Currently deployed:
vars-file (vars/users.yml):
users:
- username: "bob"
sshkey: "{{ lookup('file', 'files/ssh_keys/bob.keys') }}"
...
- username: "alice"
sshkey: "{{ lookup('file', 'files/ssh_keys/alice.keys') }}"
...
contents of bob.keys:
ssh-rsa admin-ssh-key= admin
ssh-rsa support-a-ssh-key= support-a
ssh-rsa bobs-ssh-key= hi im bob
contents of alice.keys:
ssh-rsa admin-ssh-key= admin
ssh-rsa alice-ssh-key= hi im alice
ssh-rsa accounting-clerk-ssh-key= checking your progress
ansible role main.yml file:
- name: Add SSH keys
authorized_key:
user: "{{ item.username }}"
state: "{{ item.sshkeystate }}"
key: "{{ item.sshkey }}"
exclusive: yes
with_items: "{{ users }}"
tags: [add ssh keys]
Problem Scenario:
support-a's ssh key got compromised and all users having that public key in their authorized_keys file need to be replaced with the new public key
I need to (sed) through all keys files and replace the compromised public key with the new one
I tried:
vars-file:
users:
- username: "bob"
sshkey:
- "{{ lookup('file', 'files/ssh_keys/admin.pub') }}"
- "{{ lookup('file', 'files/ssh_keys/support_a.pub') }}"
- "{{ lookup('file', 'files/ssh_keys/bob.pub') }}"
...
- username: "alice"
sshkey:
- "{{ lookup('file', 'files/ssh_keys/admin.pub') }}"
- "{{ lookup('file', 'files/ssh_keys/alice.pub') }}"
- "{{ lookup('file', 'files/ssh_keys/accounting_clerk.pub') }}"
However, I'm getting this error when executing the ansible role:
"msg": "invalid key specified: ['ssh-rsa admin-ssh-key= admin', 'ssh-rsa support-a-ssh-key= support-a', 'ssh-rsa bobs-ssh-key= hi im bob']"
I also tried something along the lines of this ( https://stackoverflow.com/a/54079374 ) solution but I guess the scenario and requirements are a bit different and there is nothing to selectattr() in my scenario (I think).
Can anyone point me to how to solve this or am I going in a completely wrong direction?
Cheers.
As best I can tell, the only part that differs in your playbook from the one you linked to is the "\n".join() part, which folds the list into one text block. As ansible correctly points out, you are providing a list[str] to an attribute that expects str
I don't believe in your circumstance you need to use selectattr or any filtering, because you want all the keys all the time. Thus:
- name: Add SSH keys
authorized_key:
user: "{{ item.username }}"
state: "{{ item.sshkeystate }}"
key: '{{ item.sshkey | join("\n") }}'
exclusive: yes
with_items: "{{ users }}"
tags: [add ssh keys]

Ansible: How to create uid's within certain range

I am currently working on a host where i have installed ansible. I have created 2 application accounts with groups with nologin and within that groups i want to add users, so that every department has their own ansible directory.
My vars look like below:
---
- hosts: localhost
become: yes
vars:
ansible_groupuser:
- name: "ansible-dictators"
ansible_groupuser_uid: "3000"
ansible_users:
- idia
- josefs
- donaldt
- kimjongu
- name: "ansible-druglords"
ansible_groupuser_uid: "3001"
ansible_users:
- pabloe
- javierg
- frankl
- rossu
Now i have 2 plays. 1 to create the Groupuser:
# This creates the groupuser
- name: Play 1 Create central ansible user and group per department
user:
name: "{{ item.name }}"
shell: "/sbin/nologin"
home: "/home/{{ item.name }}"
comment: "{{ item.name }} Group Account"
uid: "{{ item.ansible_groupuser_uid }}"
append: "yes"
with_items:
- "{{ansible_groupuser}}"
And 1 to create the "normal" users:
- name: Play 2 Create users
user:
name: "{{ item.1 }}"
shell: "/bin/bash"
home: "/home/{{ item.1 }}"
comment: "{{ item.1 }}"
groups: "{{ item.0.name }}"
append: "yes"
with_subelements:
- "{{ ansible_groupuser }}"
- ansible_users
If i run this play it creates the groupuser ansible-dictators on 3000 and ansible-druglords on 3001. idia gets 3002, josefs gets 3003 etc. It gets kinda messy, when i want to add a 3th groupuser like ansible-rockstars, it starts counting at the first available uid, 3010. What i want is to place the groupusers and the common users in 2 different ranges (2000 and 3000 for example)
When i do a with_together on the first play, like below, it works:
- name: Play1 Create central ansible user and group per department
user:
name: "{{ item.0.name }}"
shell: "/sbin/nologin"
home: "/home/{{ item.0.name }}"
comment: "{{ item.0.name }} Group Account"
uid: "{{ item.1 }}"
append: "yes"
with_together:
- "{{ansible_groupuser}}"
- "{{ range(3000,3020)|list }}"
when: item.0 != None
But when i do a with_together on the second play, it doesnt work:
- name: Create users
user:
name: "{{ item.1 }}"
shell: "/bin/bash"
home: "/home/{{ item.1 }}"
comment: "{{ item.1 }}"
groups: "{{ item.0.name }}"
append: "yes"
uid: "{{ item.2 }}"
with_together:
- "{{ ansible_groupuser }}"
- ansible_users
- "{{ range(2000,2020)|list }}"
Anyone got a suggestion how to make the second play work with a uid in a certain range? Or another suggestion how to get the uid's in different groups? To give the groupusers an uid in the vars is no problem. But i am expecting a lot of "common" users to add (+50) and i dont want to specify a uid for all of those users.
Hope it makes sense. Thanks in advance.
I think range(...) approach has a flaw: if you delete some user from your list in the future, IDs for subsequent entries will change and you can end up with messed file permissions on your system.
You can patch user module to support --firstuid/--lastuid arguments of the underlying adduser command, so you can set different range for uid generation.
But I'd suggest you to define "static" uids for top-level users in your vars file (from some predefined range, say: 3000..30xx) – this way you can safely add/remove top-level user/groups in the future.
And leave "common" users to get their ids automatically, so adding/deleting them will not mess your ids. If you like them to be from some specific range, you can modify system-wide /etc/adduser.conf with FIRST_UID=5000/LAST_UID=6000.

Ansible: Using a dictionary variable within a lookup

I am attempting to deploy ssh keys with ansible using a file lookup task that utilizes {{ item.key }} which is simply a username and I am trying to find the appropriate syntax to call for the lookup value.
# Deploy Key Task
- name: add user ssh keys
authorized_key:
user={{ item.key }}
key={{ lookup('file', 'files/{{ item.key }}.pub') }}
with_dict: users
Where users is
users:
example:
state: present
comment: "Example User"
shell: "/bin/bash"
uid: 5001
gid: 5001
From the dictionary, I am wanting to use item.key to populate the user.pub file.
Within the files directory I simply have a file called example.pub with the users public key.
Blockquote
Did some digging and this is what solved it.
- name: add user ssh keys
authorized_key:
user={{ item.key }}
key="{{ lookup('file', 'files/'+ item.key +'.pub') }}"
with_dict: users
Note: key="{{ lookup('file', 'files/'+ item.key +'.pub') }}"

Resources