22

I'm trying to re-generate ssh host keys on a handful of remote servers via ansible (and ssh-keygen), but the files don't seem to be showing up. The playbook runs OK, but the files on the remote are not altered.

I need to resort to the echo -e hackery since these remotes are running Ubuntu 14.04 and haven't the correct version of the python-pexpect available (according to ansible).

What am I missing? My playbook and output are below:

playbook

---
- hosts: all
  become: true
  gather_facts: false

  tasks:
    - name: Generate /etc/ssh/ RSA host key
      command : echo -e 'y\n'|ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C "" -N ""
      register: output
    - debug: var=output.stdout_lines

    - name: Generate /etc/ssh/ DSA host key
      command : echo -e 'y\n'|ssh-keygen -q -t dsa -f /etc/ssh/ssh_host_dsa_key -C "" -N ""
      register: output
    - debug: var=output.stdout_lines

    - name: Generate /etc/ssh/ ECDSA host key
      command : echo -e 'y\n'|ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -C "" -N ""
      register: output
    - debug: var=output.stdout_lines

output

$ ansible-playbook ./playbooks/ssh-hostkeys.yml -l myhost.mydom.com, 
SUDO password: 

PLAY [all] **********************************************************************************************

TASK [Generate /etc/ssh/ RSA host key] ******************************************************************
changed: [myhost.mydom.com]

TASK [debug] ********************************************************************************************
ok: [myhost.mydom.com] => {
    "output.stdout_lines": [
        "y", 
        "|ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C  -N "
    ]
}

TASK [Generate /etc/ssh/ DSA host key] ******************************************************************
changed: [myhost.mydom.com]

TASK [debug] ********************************************************************************************
ok: [myhost.mydom.com] => {
    "output.stdout_lines": [
        "y", 
        "|ssh-keygen -q -t dsa -f /etc/ssh/ssh_host_dsa_key -C  -N "
    ]
}

TASK [Generate /etc/ssh/ ECDSA host key] ****************************************************************
changed: [myhost.mydom.com]

TASK [debug] ********************************************************************************************
ok: [myhost.mydom.com] => {
    "output.stdout_lines": [
        "y", 
        "|ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -C  -N "
    ]
}

PLAY RECAP **********************************************************************************************
myhost.mydom.com : ok=6    changed=3    unreachable=0    failed=0  

7 Answers 7

25

As far as I know the only reason why you would need to pipe a 'y' to ssh-keygen, is if your command is replacing an existing file. In my opinion this is not a good way to do something from a configuration management tool.

You should adjust your tasks to make them idempotent. Specifically if you add the creates: filename to your command, then the new keys will only be created when they don't already exist, instead of being replaced each time you run that playbook.

---
- hosts: all
  become: true
  gather_facts: false

  tasks:
  - name: Generate /etc/ssh/ RSA host key
    command : ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C "" -N ""
    args:
      creates: /etc/ssh/ssh_host_rsa_key

  - name: Generate /etc/ssh/ DSA host key
    command : ssh-keygen -q -t dsa -f /etc/ssh/ssh_host_dsa_key -C "" -N ""
    args:
      creates: /etc/ssh/ssh_host_dsa_key

  - name: Generate /etc/ssh/ ECDSA host key
    command : ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -C "" -N ""
    args:
      creates: /etc/ssh/ssh_host_ecdsa_key

If for some reason you wanted to replace those keys for example if they were too old or something you might want to add another task to remove them. Here is a simple delete

- file:
    state: absent:
    path: "{{item}}"
  loop:
  - /etc/ssh/ssh_host_rsa_key
  - /etc/ssh/ssh_host_dsa_key
  - /etc/ssh/ssh_host_ecdsa_key

If you wanted to delete files generated before a certain time, you could use the stat module to retrieve details about this files, and setup when conditions to selectively remove them if they were older then a certain date or something.

3
  • 1
    Great example and thanks for the thoughts on maintaining idempotence. For my use case, I'm provisioning cloned Virtual Machines so there will always be keys to overwrite. I kind of need the nuclear option to just remove and replace. May 3, 2018 at 18:04
  • If you always want it to remove, then I would probably do the file: state:absent ... approach over piping stuff to ssh-keygen. Though there probably isn't that much of a difference.
    – Zoredache
    May 3, 2018 at 19:45
  • Ah, ok. that makes more sense. I didn't know about absent a couple days ago. Effectively that will delete the file prior to re-generating the key. It's a much clearer approach. Thanks. May 3, 2018 at 20:09
10

Use the special module for this task:

- name: Generate an OpenSSH keypair with the default values (4096 bits, rsa)
  openssh_keypair:
    path: /home/youruser/.ssh/id_rsa
    owner: youruser
    group: youruser

- name: Fix owner of the generated pub key
  file:
    path: /home/youruser/.ssh/id_rsa.pub
    owner: youruser
    group: youruser
1
  • 1
    Those are not ssh host keys
    – KumZ
    Feb 24, 2020 at 15:05
7

The ansible command module does not pass commands through a shell. This means you can't use shell operators such as the pipe, and that is why you are seeing the pipe symbol in the output. As far as ansible is concerned, it has executed the command echo with all of the rest of the line as arguments to echo.

If you need the command line processed by a shell, use shell instead of command.

And, there ought to be a better way to regenerate ssh host keys, but I can't find one right now...

9
  • Thanks for the shell vs. command tip (works fine now) I didn't know - still pretty new to ansible Apr 30, 2018 at 19:24
  • Regarding the last statement (at least on CentOS/RHEL) if you remove the old keys and restart the daemon host keys are regenerated for you. You'll need to restart the service anyway so this definitely seems somewhat better. Apr 30, 2018 at 19:25
  • @AaronCopley I was more referring to an Ansible role than the distro service. I'm aware that most major distros have a systemd service that generates ssh host keys. Unfortunately that service has subtle distro-specific differences (it's even different between CentOS and Fedora). A role would be a nice way to encapsulate all that, but I can't find one offhand. Apr 30, 2018 at 19:27
  • No worries, just thought I'd mention it. (You may know, but OP might not.) Apr 30, 2018 at 19:31
  • @AaronCopley - incidentally, this is what I ended up doing. the echo ... bit didn't work after a second run (I was testing in /tmp/ which the keys did not exist the first time around). I resorted to removing the host keys first, as you mention, and the generating new ones. As far as having the keys automatically regenerated, this is dependent on your distribution, correct? Not all Linux distros use systemd. Apr 30, 2018 at 19:40
3

Another option is to use user module. Positive side of this is that you'll get an idempotent task. Here is an example how to generate ssh keys on localhost:

- name: Generate ssh keys
  local_action:
    module: "user"
    name: "{{ lookup('env','USER') }}"
    generate_ssh_key: true
    ssh_key_type: "{{ item.0 }}"
    ssh_key_bits: "{{ item.1 }}"
    ssh_key_file: "{{ playbook_dir }}/{{ item.0 }}_{{ item.1 }}_key"
  with_together:
  - [ 'rsa', 'dsa' ]
  - [ 2048, 1024 ]
  loop_control:
    label: "{{ item.0 }}_{{ item.1 }}_key"

- name: Copy generated ssh keys to remote machine
  copy:
    src: "{{ playbook_dir }}/{{ item.0 }}_{{ item.1 }}_key"
    dest: "/etc/ssh/ssh_host_{{ item.0 }}_key{{ item.1 }}"
  with_nested:
  - [ 'rsa', 'dsa' ]
  - [ '', '.pub' ]
  notify:
  - Restart sshd
  loop_control:
    label: "/etc/ssh/ssh_host_{{ item.0 }}_key{{ item.1 }}"
5
  • 1
    Isn't that for user keys, not host keys?
    – MadHatter
    Feb 15, 2019 at 8:17
  • You can use it for host keys too because it's the same thing actually. Just don't forget to restore selinux context if you use SELinux in enforcing mode Feb 15, 2019 at 8:34
  • It's not clear to me from the documentation that you can. If you'd rewrite your answer so it explicitly shows host keys being created, I'd remove my downvote.
    – MadHatter
    Feb 15, 2019 at 8:51
  • Okay, maybe it was unclear. I've added another task to explain how to copy these keys at a remote machine. And of course it's only my case (I need the same keys on a few machines for a cluster so I need to generate them on localhost) and I'm pretty sure that you can use 'user' module to generate keys for ssh server on the remote machine (look at 'ssh_key_file') Feb 15, 2019 at 9:12
  • I'm still not sure it's anything other than a hack (not least because it leaves one user with a copy of the host private key!) but at least it's something that will now answer the question as asked, so I've removed my downvote.
    – MadHatter
    Feb 15, 2019 at 9:17
1

sorry, but the i could not use "creates" in a task. i obtained the following error:

ERROR! 'creates' is not a valid attribute for a Task

consquently, i use the following tasks:

- name: remove existing ssh_host keys
  file: path={{ item }} state=absent
  with_items:
    - "/etc/ssh/ssh_host_rsa_key"
    - "/etc/ssh/ssh_host_dsa_key"
    - "/etc/ssh/ssh_host_ecdsa_key"

- name: Generate /etc/ssh/ RSA host key
  command : ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C "" -N ""

- name: Generate /etc/ssh/ DSA host key
  command : ssh-keygen -q -t dsa -f /etc/ssh/ssh_host_dsa_key -C "" -N ""

- name: Generate /etc/ssh/ ECDSA host key
  command : ssh-keygen -q -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -C "" -N ""
2
  • 2
    Use a current version of Ansible. Jul 10, 2018 at 18:40
  • You are right, my Ansible version is a bit old : 2.0.0.2... (On Ubuntu 16.04). I have to change ! Jul 11, 2018 at 6:22
1

@Zoredache has the correct answer but it fails (noted by @MaxiReglisse) for recent versions of Ansible. Use the following code instead:

---
- hosts: all
  become: true
  gather_facts: false

  tasks:
  - name: Generate /etc/ssh/ RSA host key
    command : ssh-keygen -q -t rsa -f /etc/ssh/ssh_host_rsa_key -C "" -N ""
    args:
      creates: /etc/ssh/ssh_host_rsa_key
1

Use the openssh_keypair and authorized_key module to create and deploy the keys at the same time without saving it into your ansible host.

- openssh_keypair:
    group: root
    owner: root
    path: /some/path/in/your/server
    register: ssh_key

- name: Store public key into origin
  delegate_to: central_server_name
  authorized_key:
     key: "{{ssh_key.public_key}}"
     comment: "{{ansible_hostname}}"
     user: any_user_on_central

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .