community.crypto
community.crypto copied to clipboard
Add support for SSH certificates using ecdsa-sk or ed25519-sk public keys
SUMMARY
Fixes #796
ISSUE TYPE
- Feature Pull Request
COMPONENT NAME
module_utils.openssh.certificate
ADDITIONAL INFORMATION
The openssh.certificate module can both sign using FIDO2 SSH keys and create signatures for FIDO2 SSH keys, but the overall task results in a failure status because it can not understand the certificate it has created.
This PR adds two new types of public keys and certificates to the load function which match the ssh-keygen key types ecdsa-sk and ed25519-sk.
Steps to reproduce
This requires a hardware FIDO2 key, e.g. Yubikey.
Create FIDO2 keys using ssh-keygen -t ecdsa-sk -f /tmp/id_ecdsa_sk -N '' and ssh-keygen -t ecdsa-sk -f /tmp/id_ed25519_sk -N '', then use the example below to sign the ECDSA key using the Ed25519 key.
sign-key.yml
---
- name: Sign OpenSSH user key
hosts: localhost
gather_facts: false
tasks:
# Generate an OpenSSH user certificate that is valid for 1 hour from now and will be regenerated
# if it is valid for less than 10 minutes from the time the module is being run
- name: Certify OpenSSH user key using master key
community.crypto.openssh_cert:
type: user
signing_key: /tmp/id_ed25519_sk
public_key: /tmp/id_ecdsa_sk.pub
path: /tmp/id_ecdsa_sk-cert.pub
valid_from: -1m
valid_to: +1h
valid_at: +10m
ignore_timestamps: true
principals:
- myname
options:
- clear
- permit-pty
- no-touch-required
$ ansible-playbook -c local -i /dev/null -v sign-key.yml
Without this PR:
No config file found; using defaults
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'
PLAY [Sign OpenSSH user key] **********************************************************
TASK [Certify OpenSSH user key using master key] **************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Unable to read new certificate: Invalid certificate format identifier: b'[email protected]'"}
PLAY RECAP ****************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
The full traceback is:
File "/tmp/ansible_community.crypto.openssh_cert_payload_kpyxp1cv/ansible_community.crypto.openssh_cert_payload.zip/ansible_collections/community/crypto/plugins/modules/openssh_cert.py", line 469, in _generate
self.data = OpensshCertificate.load(self.path)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/tmp/ansible_community.crypto.openssh_cert_payload_kpyxp1cv/ansible_community.crypto.openssh_cert_payload.zip/ansible_collections/community/crypto/plugins/module_utils/openssh/certificate.py", line 482, in load
raise ValueError("Invalid certificate format identifier: %s" % format_identifier)
With this PR applied:
No config file found; using defaults
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'
PLAY [Sign OpenSSH user key] **********************************************************
TASK [Certify OpenSSH user key using master key] **************************************
changed: [localhost] => {"changed": true, "filename": "/tmp/id_ecdsa_sk-cert.pub", "info": ["Type: [email protected] user certificate", "Public key: ECDSA-SK-CERT SHA256:5+FIpbU8zV3jqDqNLoC8XIS2vfeoGNxARJVVhucoR8c", "Signing CA: ED25519-SK SHA256:rQJvUPDwKZOst1h5MnDrum4bhEU6KnEi5HNb+4qYYm0 (using [email protected])", "Key ID: \"\"", "Serial: 0", "Valid: from 2024-10-29T11:33:01 to 2024-10-29T12:34:01", "Principals: myname", "Critical Options: (none)", "Extensions: no-touch-required permit-pty"], "type": "user"}
PLAY RECAP ****************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
@jnohlgard ping