diff --git a/changelogs/fragments/known_hosts_cert-authority_keys.yml b/changelogs/fragments/known_hosts_cert-authority_keys.yml new file mode 100644 index 00000000000..0f3e31709c4 --- /dev/null +++ b/changelogs/fragments/known_hosts_cert-authority_keys.yml @@ -0,0 +1,2 @@ +bugfixes: + - known_hosts - Fix issue with `@cert-authority` entries in known_hosts incorrectly being removed. diff --git a/lib/ansible/modules/known_hosts.py b/lib/ansible/modules/known_hosts.py index b742384fdbd..8235258c664 100644 --- a/lib/ansible/modules/known_hosts.py +++ b/lib/ansible/modules/known_hosts.py @@ -273,12 +273,20 @@ def search_for_host_key(module, host, key, path, sshkeygen): module.fail_json(msg="failed to parse output of ssh-keygen for line number: '%s'" % l) else: found_key = normalize_known_hosts_key(l) - if new_key['host'][:3] == '|1|' and found_key['host'][:3] == '|1|': # do not change host hash if already hashed - new_key['host'] = found_key['host'] - if new_key == found_key: # found a match - return True, False, found_line # found exactly the same key, don't replace - elif new_key['type'] == found_key['type']: # found a different key for the same key type - return True, True, found_line + + if 'options' in found_key and found_key['options'][:15] == '@cert-authority': + if new_key == found_key: # found a match + return True, False, found_line # found exactly the same key, don't replace + elif 'options' in found_key and found_key['options'][:7] == '@revoke': + if new_key == found_key: # found a match + return True, False, found_line # found exactly the same key, don't replace + else: + if new_key['host'][:3] == '|1|' and found_key['host'][:3] == '|1|': # do not change host hash if already hashed + new_key['host'] = found_key['host'] + if new_key == found_key: # found a match + return True, False, found_line # found exactly the same key, don't replace + elif new_key['type'] == found_key['type']: # found a different key for the same key type + return True, True, found_line # No match found, return found and replace, but no line return True, True, None diff --git a/test/integration/targets/known_hosts/defaults/main.yml b/test/integration/targets/known_hosts/defaults/main.yml index cd438430fa4..849fb4b9bd8 100644 --- a/test/integration/targets/known_hosts/defaults/main.yml +++ b/test/integration/targets/known_hosts/defaults/main.yml @@ -1,6 +1,11 @@ --- example_org_rsa_key: > example.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAglyZmHHWskQ9wkh8LYbIqzvg99/oloneH7BaZ02ripJUy/2Zynv4tgUfm9fdXvAb1XXCEuTRnts9FBer87+voU0FPRgx3CfY9Sgr0FspUjnm4lqs53FIab1psddAaS7/F7lrnjl6VqBtPwMRQZG7qlml5uogGJwYJHxX0PGtsdoTJsM= - example_org_ed25519_key: > example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIzlnSq5ESxLgW0avvPk3j7zLV59hcAPkxrMNdnZMKP2 +host_example_com_ed25519_key: > + host.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFp8VtD59XAcxkj1qbfCtB1in9nm5WiipORjtVJUBA6I +example_com_ed25519_ca: > + @cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPx6KAHnQhaWdYQoaclJMWfneZckvYOkZ32gUJO1zzJK +host_example_com_ed25519_signedhost: > + host.example.com ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIHgrfzePvYcPiRDh/3yKt2sJBk6mftlLGPpAlgveY8amAAAAIE/humEfyhaw5kawq/RC8tOoVJFgu6v+AYV2koz4bULNAAAAAAAAAAAAAAACAAAAFGhvc3QuZXhhbXBsZS5jb20ucHViAAAAFAAAABBob3N0LmV4YW1wbGUuY29tAAAAAAAAAAD//////////wAAAAAAAAAAAAAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAg/HooAedCFpZ1hChpyUkxZ+d5lyS9g6RnfaBQk7XPMkoAAABTAAAAC3NzaC1lZDI1NTE5AAAAQPrSxFZ57dZvHy+ZqhudHBj5C1xU/aiAcMjbpyg3PwR/T/ym8B299uyhRj4g6wbby389xuTFkIYYgGlzh1vAkA0= diff --git a/test/integration/targets/known_hosts/files/existing_known_hosts b/test/integration/targets/known_hosts/files/existing_known_hosts index 2564f409b8b..7aac98fdde5 100644 --- a/test/integration/targets/known_hosts/files/existing_known_hosts +++ b/test/integration/targets/known_hosts/files/existing_known_hosts @@ -2,4 +2,5 @@ example.com ssh-dss AAAAB3NzaC1kc3MAAACBALT8YHxZ59d8yX4oQNPbpdK9AMPRQGKFY9X13S2f |1|d71/U7CbOH3Su+d2zxlbmiNfXtI=|g2YSPAVoK7bmg16FCOOPKTZe2BM= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ== |1|L0TqxOhAVh6mLZ2lbHdTv3owun0=|vn0La5pbHNxin3XzQQdvaOulvVU= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCNLCAA/SjVF3jkmlAlkgh+GtZdgxtusHaK66fcA7XSgCpQOdri1dGmND6pQDGwsxiKMy4Ou1GB2DR4N0G9T5E8= |1|WPo7yAOdlQKLSuRatNJCmDoga0k=|D/QybGglKokWuEQUe9Okpy5uSh0= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCNLCAA/SjVF3jkmlAlkgh+GtZdgxtusHaK66fcA7XSgCpQOdri1dGmND6pQDGwsxiKMy4Ou1GB2DR4N0G9T5E8= +@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDXh1gk2xgS2MekPvo7ZEKiOT7HoyvOAzai2GqoLXGHO # example.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM6OSqweGdPdQ/metQaf738AdN3P+itYp1AypOTgXkyj root@localhost diff --git a/test/integration/targets/known_hosts/tasks/main.yml b/test/integration/targets/known_hosts/tasks/main.yml index d5ffec4dc6d..2e5ecc7ebbc 100644 --- a/test/integration/targets/known_hosts/tasks/main.yml +++ b/test/integration/targets/known_hosts/tasks/main.yml @@ -57,7 +57,8 @@ that: - 'result is changed' - 'known_hosts.stdout_lines[0].startswith("example.com")' - - 'known_hosts.stdout_lines[4].startswith("# example.net")' + - 'known_hosts.stdout_lines[4].startswith("@cert-authority")' + - 'known_hosts.stdout_lines[5].startswith("# example.net")' - 'known_hosts.stdout_lines[-1].strip() == example_org_rsa_key.strip()' # test idempotence of addition @@ -222,7 +223,7 @@ that: - 'result is changed' - 'known_hosts_v5.stdout_lines[0].startswith("example.com")' - - 'known_hosts_v5.stdout_lines[4].startswith("# example.net")' + - 'known_hosts_v5.stdout_lines[5].startswith("# example.net")' - 'known_hosts_v5.stdout_lines[-1].strip().startswith("|1|")' - 'known_hosts_v5.stdout_lines[-1].strip().endswith(example_org_rsa_key.strip().split()[-1])' @@ -342,7 +343,7 @@ - name: assert the plaintext host is there assert: that: - - 'known_hosts_v10.stdout_lines[5].strip() == example_org_rsa_key.strip()' + - 'known_hosts_v10.stdout_lines[6].strip() == example_org_rsa_key.strip()' # ... and remove the host again for the next test @@ -378,6 +379,105 @@ that: - 'known_hosts_v11.stdout_lines[-1].strip().endswith("RANDOM=")' +- name: add the ed25519 host key + known_hosts: + name: host.example.com + key: "{{ host_example_com_ed25519_key }}" + state: present + path: "{{remote_tmp_dir}}/known_hosts" + register: result + +- name: get the file content + command: "cat {{remote_tmp_dir}}/known_hosts" + register: known_hosts_v12 + +- name: assert that the key was added and ordering preserved + assert: + that: + - 'result is changed' + - 'known_hosts_v12.stdout_lines[0].startswith("example.com")' + - 'known_hosts_v12.stdout_lines[4].startswith("@cert-authority")' + - 'known_hosts_v12.stdout_lines[5].startswith("# example.net")' + - 'known_hosts_v12.stdout_lines[-1].strip() == host_example_com_ed25519_key.strip()' + +- name: add the ed25519 ca key + known_hosts: + name: '*.example.com' + key: "{{ example_com_ed25519_ca }}" + state: present + path: "{{remote_tmp_dir}}/known_hosts" + register: result + +- name: get the file content + command: "cat {{remote_tmp_dir}}/known_hosts" + register: known_hosts_v13 + +- name: assert that the key was added and ordering preserved + assert: + that: + - 'result is changed' + - 'known_hosts_v13.stdout_lines[0].startswith("example.com")' + - 'known_hosts_v13.stdout_lines[4].startswith("@cert-authority")' + - 'known_hosts_v13.stdout_lines[5].startswith("# example.net")' + - 'known_hosts_v13.stdout_lines[-1].strip() == example_com_ed25519_ca.strip()' + +- name: Remove the ed25519 ca key + known_hosts: + name: '*.example.com' + key: "{{ example_com_ed25519_ca }}" + state: absent + path: "{{remote_tmp_dir}}/known_hosts" + register: result + +- name: get the file content + command: "cat {{remote_tmp_dir}}/known_hosts" + register: known_hosts_v14 + +- name: assert that the key was removed and ordering preserved + assert: + that: + - 'result is changed' + - 'known_hosts_v12.stdout == known_hosts_v14.stdout' + +- name: add the revoked ed25519 host key + known_hosts: + name: 'host.example.com' + key: "@revoked {{ host_example_com_ed25519_signedhost }}" + state: present + path: "{{remote_tmp_dir}}/known_hosts" + register: result + +- name: get the file content + command: "cat {{remote_tmp_dir}}/known_hosts" + register: known_hosts_v15 + +- name: assert that the key was added and ordering preserved + assert: + that: + - 'result is changed' + - 'known_hosts_v15.stdout_lines[0].startswith("example.com")' + - 'known_hosts_v15.stdout_lines[4].startswith("@cert-authority")' + - 'known_hosts_v15.stdout_lines[5].startswith("# example.net")' + - 'known_hosts_v15.stdout_lines[-1].strip() == "@revoked " ~ host_example_com_ed25519_signedhost.strip()' + +- name: remove the revoked ed25519 host key + known_hosts: + name: 'host.example.com' + key: "@revoked {{ host_example_com_ed25519_signedhost }}" + state: absent + path: "{{remote_tmp_dir}}/known_hosts" + register: result + +- name: get the file content + command: "cat {{remote_tmp_dir}}/known_hosts" + register: known_hosts_v16 + +- name: assert that the key was removed and ordering preserved + assert: + that: + - 'result is changed' + - 'known_hosts_v12.stdout == known_hosts_v16.stdout' + # test errors - name: Try using a comma separated list of hosts