Can Ansible deploy public SSH key asking password only once? - ansible

I wonder how to copy my SSH public key to many hosts using Ansible.
First attempt:
ansible all -i inventory -m local_action -a "ssh-copy-id {{ inventory_hostname }}" --ask-pass
But I have the error The module local_action was not found in configured module paths.
Second attempt using a playbook:
- hosts: all
become: no
tasks:
- local_action: command ssh-copy-id {{ inventory_hostname }}
Finally I have entered my password for each managed host:
ansible all -i inventory --list-hosts | while read h ; do ssh-copy-id "$h" ; done
How to fill password only once while deploying public SSH key to many hosts?
EDIT: I have succeeded to copy my SSH public key to multiple remote hosts using the following playbook from the Konstantin Suvorov's answer.
- hosts: all
tasks:
- authorized_key:
key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
The field user should be mandatory according to the documentation but it seems to work without. Therefore the above generic playbook may be used for any user when used with this command line:
ansible-playbook -i inventory authorized_key.yml -u "$USER" -k

Why don't you use authorized_key module?
- hosts: all
tasks:
- authorized_key:
user: remote_user_name
state: present
key: "{{ lookup('file', '/local/path/.ssh/id_rsa.pub') }}"
and run playbook with -u remote_user_name -k

Related

How to add SSH fingerprints to known hosts in Ansible?

I have the following inventory file:
[all]
192.168.1.107
192.168.1.108
192.168.1.109
I want to add fingerprints for these hosts to known_hosts file on local machine.
I know that I can use the ansible.builtin.known_hosts but based on the docs:
Name parameter must match with "hostname" or "ip" present in key
attribute.
it seems like I must already have keys generated and I must have three sets of keys - one set per host. I would like to have just one key for all my hosts.
Right now I can use this:
- name: accept new remote host ssh fingerprints at the local host
shell: "ssh-keyscan -t 'ecdsa' {{item}} >> {{ssh_dir}}known_hosts"
with_inventory_hostnames:
- all
but the problem with this approach is that it is not idempotent - if I run it three times it will add three similar lines in the known_hosts file.
Another solution would be to check the known_hosts file for presence of a host ip and add it only if it is not present, but I could not figure out how to use variables in when condition to check for more than one host.
So the question is how can I add hosts fingerprints to local known_hosts file before generating a set of private/public keys in idempotent manner?
Here in my answer to "How to include all host keys from all hosts in group" I created a small Ansible look-up module host_ssh_keys to extract public SSH keys from the host inventory. Adding all hosts' public ssh keys to /etc/ssh/ssh_known_hosts is then as simple as this, thanks to Ansible's integration of loops with look-up plugins:
- name: Add public keys of all inventory hosts to known_hosts
ansible.builtin.known_hosts:
path: /etc/ssh/ssh_known_hosts
name: "{{ item.host }}"
key: "{{ item.known_hosts }}"
with_host_ssh_keys: "{{ ansible_play_hosts }}"
For public SSH-Keys I use this one:
- hosts: localhost
tasks:
- set_fact:
linuxkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
check_mode: no
- hosts: all
tasks:
- shell:
cmd: "sudo su - {{ application_user }}"
stdin: "[[ ! `grep \"{{ hostvars['localhost']['linuxkey'] }}\" ~/.ssh/authorized_keys` ]] && echo '{{ hostvars['localhost']['linuxkey'] }}' >> ~/.ssh/authorized_keys"
warn: no
executable: /bin/bash
register: results
failed_when: results.rc not in [0,1]
I think you can easy adapt it for known_hosts file

Ansible get specified --key-file in task

I'm specifying the custom ssh key file with the following command:
ansible-playbook playbook.yml -i inventory.cfg --key-file ssh_keyfile
Now I want to add the same key to the host I'm managing with Ansible with the following task:
- name: add ssh key
authorized_key:
user: "{{ user }}"
state: present
key: "{{ lookup('file', ssh_keyfile) }}"
How can I get the value of the --key-file specified in command line when running playbook, as a variable inside the playbook?
The --key-file ssh_keyfile is a private key file path which will be used to authenticate to the remote server.
Ansible authorized_key module will look for public key so you have to use lookup for that

How to perform a cyberarkpassword lookup for all hosts in an inventory group and write the keys out to individual pem files?

Ansible version 2.7.9
I'm writing an ansible playbook to deploy an piece of software to a linux environment. SSH access to these systems is protected by a CPM (Cyberark), used as an ssh key manager.
I've got most of the logic figured out, save for one piece. The playbook needs to loop through hosts in an inventory group, lookup the ssh private key in Cyberark for each host and then use each key to ssh into each host in the inventory group to install the software. I'm struggling with how to make that work in ansible.
I've read through the add_host and cyberarkpassword documentation, as well about 4 hours worth of searching stackoverflow and blogs, and couldn't find a single example even close to what I'm trying to do.
As far as how I think it should work:
Using the cyberarkpassword lookup module, loop through hosts in inventory group specified by {{ env }}. Value for this will be passed in through --extra-args.
Retrieve the ssh private key for each host.
Register the output from the lookup, and copy to disk, again looping through each host, and naming the file with {{ inventory_hostname }}.pem
Finally, to consume it in the next play, set a variable ansible_ssh_common_args: "-o StrictHostKeyChecking=no -i {{ deploy_temp_dir}}/keys/{{ inventory_hostname }}.pem"
But I can't figure out how to put the loop-lookup-write to disk piece together.
Sample inventory file
[local]
localhost
[local:vars]
ansible_connection=local
[corp:children]
corp-onprem-dev
corp-onprem-stage
corp-onprem-prod
corp-cloud-dev
corp-cloud-stage
corp-cloud-dev
[corp-onprem-dev]
host1
host2
host3
[corp-onprem-stage]
host1
host2
host3
[corp-onprem-prod]
host1
host2
host3
[corp-cloud-dev]
[corp-cloud-stage]
[corp-cloud-prod]
deploy.yml -- this code does not work, just my attempt at figuring it out.
- name: retrieve ssh keys for hosts in the specified group, and write them to disk
hosts: local
gather_facts: no
tasks:
- name: lookup ssh private key for each host
debug: msg={{ lookup("cyberarkpassword", cyquery)}}
vars:
cyquery:
appid: 'myapp'
query: 'Safe=mysafe;Folder=Root;Object={{ env[0] }}'
output: 'Password'
loop: groups['{{ env }}']
register: sshkeys
- name: Copy ssh key to disk
copy:
content: "{{ sshkeys }}"
dest: "{{ deploy_temp_dir }}/keys/{{ env[0] }}.pem"
mode: 0600
loop: groups['{{ env }}']
It is not clear how to "use each (private) key to ssh into each host".
To loop through hosts in an inventory group, lookup the ssh private key in Cyberark for each host and then use each key to ssh into each host in the inventory group.
Let's assume localhost (controller) is able to connect the hosts.
Take a look at the content of the variable sshkeys
- debug:
var: sshkeys
Among the lists, you'll probably see the 2 items that you're looking for. (Fit the code to what you get.)
sshkeys.results[].item ...... inventory_hostname
sshkeys.results[].password ... ssh private key
Use template to store the keys in the files. Because the play is running at the localhost delegate_to shall be used to store the files at hosts.
- template:
src: hostname.pem.j2
dest: "{{ deploy_temp_dir }}/keys/{{ item.item }}.pem"
loop: "{{ sshkeys.results }}"
delegate_to: "{{ item.item }}"
.
$ cat hostname.pem.j2
{{ item.password }}
(Not tested. I don't have CyberArk. Storing passwords in disk files may violate security standards.)

Ansible Hostname as variable in ansible_user

Need help with ansible. In our company we use following method to ssh to a server.
If IP of server is 172.16.1.8 , Username would be "empname~id~serverIP" e.g. john~1234~172.16.1.8 . So following ssh command is used -
> ssh john~1234~172.16.1.8#172.16.1.8 -i key.pem
Basically username has the hostname as a variable.
Now our inventory has just IPs with group web.
> cat inventory.txt
[web]
172.16.1.8
172.16.x.y
172.16.y.z
My playbook yml has ansible user as following.
> cat uptime.yml
- hosts: web
vars:
ansible_ssh_port: xxxx
ansible_user: john~1234~{{inventory_hostname}}
tasks:
- name: Run uptime command
shell: uptime
However, when I use following ansible-playbook command, it gives error for incorrect username.
> ansible-playbook -v uptime.yml -i inventory.txt --private-key=key.pem
Please help me find correct ansible_user in playbook which has hostname as a variable inside.
You can define ansible_user in group_vars/web.yml
group_vars/web.yml:
---
ansible_ssh_port: xxxx
ansible_user: "john~1234~{{ inventory_hostname }}"
Using a group var helped -
ansible_ssh_port: xxxx
ansible_user: "john~1234~{{ inventory_hostname }}"

How to add a host to the known_host file with ansible?

I want to add the ssh key for my private git server to the known_hosts file with ansible 1.9.3 but it doesn't work.
I have the following entry in my playbook:
- name: add SSH host key
known_hosts: name='myhost.com'
key="{{ lookup('file', 'host_key.pub') }}"
I have copied /etc/ssh/ssh_host_rsa_key.pub to host_key.pub and the file looks like:
ssh-rsa AAAAB3NzaC1... root#myhost.com
If I run my playbook I always get the following error message:
TASK: [add SSH host key]
******************************************************
failed: [default] => {"cmd": "/usr/bin/ssh-keygen -F myhost.com -f /tmp/tmpe5KNIW", "failed": true, "rc": 1}
What I am doing wrong?
You can directly use ssh-keyscan within the ansible task:
- name: Ensure servers are present in known_hosts file
known_hosts:
name: "{{ hostvars[item].ansible_host }}"
state: present
key: "{{ lookup('pipe', 'ssh-keyscan {{ hostvars[item].ansible_host }}') }}"
hash_host: true
with_items: "{{ groups.servers }}"
In the above snipped, we iterate over all hosts in the group "servers" defined in your inventory, use ssh-keyscan on them, read the result with pipe and add it using known_hosts.
If you have only one host that you want to add, it's even simpler:
- name: Ensure server is present in known_hosts file
known_hosts:
name: "myhost.com"
state: present
key: "{{ lookup('pipe', 'ssh-keyscan myhost.com') }}"
hash_host: true
Whether you need hash_host or not depends on your system.
Your copy of the remote host public key needs a name, that name needs to match what you specify for your known hosts.
In your case, prepend "myhost.com " to your host_key.pub key file as follows:
myhost.com ssh-rsa AAAAB3NzaC1... root#myhost.com
Reference:
Ansible known_hosts module, specifically the name parameter
Use ssh-keyscan to generate host_key.pub is another way.
ssh-keyscan myhost.com > host_key.pub
This command will generate the format like this.
$ ssh-keyscan github.com > github.com.pub
# github.com SSH-2.0-libssh-0.7.0
# github.com SSH-2.0-libssh-0.7.0
$ cat github.com.pub
github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==

Resources