ansible-collections / community.crypto

The community.crypto collection for Ansible.
https://galaxy.ansible.com/ui/repo/published/community/crypto/
Other
100 stars 89 forks source link

openssl_publickey fails to extract public key if private key is in OpenSSH format #528

Closed sieberst closed 1 year ago

sieberst commented 2 years ago
SUMMARY

I have an ansible playbook which creates an ssh key pair if it doesn't already exists in .ssh and extracts the public key from the private key if the private key already exists. The latter fails with the error "Wrong passphrase provided for private key" if I use a ecdsa private key stored in OpenSSH format (which is the default). If the private key is stored in the old pkcs1 format instead, the step works as expected.

ISSUE TYPE
COMPONENT NAME

community.crypto.openssl_publickey

ANSIBLE VERSION
ansible [core 2.13.6]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/siebert/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/siebert/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.10.6 (main, Nov  2 2022, 18:53:38) [GCC 11.3.0]
  jinja version = 3.0.3
  libyaml = True
COLLECTION VERSION
# /usr/lib/python3/dist-packages/ansible_collections
Collection       Version
---------------- -------
community.crypto 2.8.1  
CONFIGURATION
OS / ENVIRONMENT

Ubuntu 22.04

STEPS TO REPRODUCE
- name: Generate ssh key if there is no key available
  hosts: 127.0.0.1
  connection: local

  tasks:
    - name: Create .ssh directory
      file:
        path: ~/.ssh
        state: directory
        mode: '0700'

    - name: Check if ~/.ssh/id_ecdsa exists
      stat:
        path: ~/.ssh/id_ecdsa
      register: id_ecdsa_stat

    - name: Ensure the correct access rights if id_ecdsa exists
      file:
        path: ~/.ssh/id_ecdsa
        mode: '0600'
      when: id_ecdsa_stat.stat.exists

    - name: Extract the public key from id_ecdsa private key
      openssl_publickey:
        path: ~/.ssh/id_ecdsa.pub
        privatekey_path: ~/.ssh/id_ecdsa
        format: OpenSSH
      when: id_ecdsa_stat.stat.exists

    - name: Generate SSH key id_ecdsa if no other key exists
      openssh_keypair:
        path: ~/.ssh/id_ecdsa
        backend: cryptography
        # With the default format (ssh) the extraction of the public key from the private key fails
        #private_key_format: pkcs1
        private_key_format: ssh
        type: ecdsa
        size: 521  # Not a typo!
        state: present
        force: false
      when: not id_ecdsa_stat.stat.exists
EXPECTED RESULTS

The playbook should work without errors if run a second time

ACTUAL RESULTS
siebert@ubuntu2204ansible:/mnt/hgfs/ansible$ rm -rf ~/.ssh
siebert@ubuntu2204ansible:/mnt/hgfs/ansible$ ansible-playbook ssh_issue.yaml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Generate ssh key if there is no key available] ******************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Create .ssh directory] ******************************************************************************************************************************************************************************
changed: [127.0.0.1]

TASK [Check if ~/.ssh/id_ecdsa exists] ********************************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Ensure the correct access rights if id_ecdsa exists] ************************************************************************************************************************************************
skipping: [127.0.0.1]

TASK [Extract the public key from id_ecdsa private key] ***************************************************************************************************************************************************
skipping: [127.0.0.1]

TASK [Generate SSH key id_ecdsa if no other key exists] ***************************************************************************************************************************************************
changed: [127.0.0.1]

PLAY RECAP ************************************************************************************************************************************************************************************************
127.0.0.1                  : ok=4    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

siebert@ubuntu2204ansible:/mnt/hgfs/ansible$ ansible-playbook ssh_issue.yaml 
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Generate ssh key if there is no key available] ******************************************************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Create .ssh directory] ******************************************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Check if ~/.ssh/id_ecdsa exists] ********************************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Ensure the correct access rights if id_ecdsa exists] ************************************************************************************************************************************************
ok: [127.0.0.1]

TASK [Extract the public key from id_ecdsa private key] ***************************************************************************************************************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": false, "msg": "Wrong passphrase provided for private key"}

PLAY RECAP ************************************************************************************************************************************************************************************************
127.0.0.1                  : ok=4    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
felixfontein commented 2 years ago

The module does not support OpenSSH private keys, and also does not advertise in the documentation that it does. So this is not a bug, but a feature request.

sieberst commented 1 year ago

Sure, the documentation doesn't advertise that OpenSSH private keys are supported, but I don't find any information about which formats are supported. Given that the OpenSSH format is supported for the generated public key and OpenSSH is the default format for ecdsa private keys generated by openssh_keypair, I find the missing support to read OpenSSH private keys surprising enough to consider it a bug.

In a related question, why is the task trying to read the private key at all (and fails) if the public key already exists and there should be nothing to do for the task (as "force" isn't set)?

felixfontein commented 1 year ago

Sure, the documentation doesn't advertise that OpenSSH private keys are supported, but I don't find any information about which formats are supported.

The documentation says "TLS/SSL private key", which isn't very precise. We should definitely improve that in the documentation.

Given that the OpenSSH format is supported for the generated public key and OpenSSH is the default format for ecdsa private keys generated by openssh_keypair, I find the missing support to read OpenSSH private keys surprising enough to consider it a bug.

This module is about OpenSSL private keys, not OpenSSH private keys. That it allow to write OpenSSH public keys is a feature, but does not imply it can load OpenSSH private keys.

In a related question, why is the task trying to read the private key at all (and fails) if the public key already exists and there should be nothing to do for the task (as "force" isn't set)?

For idempotency the module has to check whether the private key actually fits to the public key, in case the private key has been regenerated. That's only possible if the private key can be read.