diff options
Diffstat (limited to 'ansible_collections/community/crypto/tests')
30 files changed, 1609 insertions, 1 deletions
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases new file mode 100644 index 000000000..b7f6d4f48 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases @@ -0,0 +1,10 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/generic/1 +azp/posix/1 +cloud/acme + +# For some reason connecting to helper containers does not work on the Alpine VMs +skip/alpine diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml new file mode 100644 index 000000000..2e8ad10b8 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_acme + - setup_remote_tmp_dir diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml new file mode 100644 index 000000000..28a889684 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml @@ -0,0 +1,154 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- vars: + certificate_name: cert-1 + subject_alt_name: DNS:example.com + account_email: example@example.org + block: + - name: Generate account key + openssl_privatekey: + path: "{{ remote_tmp_dir }}/account-ec256.pem" + type: ECC + curve: secp256r1 + force: true + - name: Create cert private key + openssl_privatekey: + path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key" + type: ECC + curve: secp256r1 + force: true + - name: Create cert CSR + openssl_csr: + path: "{{ remote_tmp_dir }}/{{ certificate_name }}.csr" + privatekey_path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key" + subject_alt_name: "{{ subject_alt_name }}" + - name: Start process of obtaining certificate + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + modify_account: true + csr: "{{ remote_tmp_dir }}/{{ certificate_name }}.csr" + dest: "{{ remote_tmp_dir }}/{{ certificate_name }}.pem" + challenge: http-01 + force: true + terms_agreed: true + account_email: "{{ account_email }}" + register: certificate_data + +- name: Inspect order + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + url: "{{ certificate_data.order_uri }}" + method: get + register: order_1 +- name: Show order + debug: + var: order_1.output_json + +- name: Deactivate order (check mode) + acme_certificate_deactivate_authz: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + order_uri: "{{ certificate_data.order_uri }}" + check_mode: true + register: deactivate_1 + +- name: Inspect order again + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + url: "{{ certificate_data.order_uri }}" + method: get + register: order_2 +- name: Show order + debug: + var: order_2.output_json + +- name: Deactivate order + acme_certificate_deactivate_authz: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + order_uri: "{{ certificate_data.order_uri }}" + register: deactivate_2 + +- name: Inspect order again + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + url: "{{ certificate_data.order_uri }}" + method: get + register: order_3 +- name: Show order + debug: + var: order_3.output_json + +- name: Deactivate order (check mode, idempotent) + acme_certificate_deactivate_authz: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + order_uri: "{{ certificate_data.order_uri }}" + check_mode: true + register: deactivate_3 + +- name: Inspect order again + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + url: "{{ certificate_data.order_uri }}" + method: get + register: order_4 +- name: Show order + debug: + var: order_4.output_json + +- name: Deactivate order (idempotent) + acme_certificate_deactivate_authz: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + order_uri: "{{ certificate_data.order_uri }}" + register: deactivate_4 + +- name: Inspect order again + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: false + account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem" + account_uri: "{{ certificate_data.account_uri }}" + url: "{{ certificate_data.order_uri }}" + method: get + register: order_5 +- name: Show order + debug: + var: order_5.output_json diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml new file mode 100644 index 000000000..68d47973d --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml @@ -0,0 +1,40 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ remote_tmp_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml new file mode 100644 index 000000000..603c7d7cc --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml @@ -0,0 +1,17 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Checks + assert: + that: + - order_1.output_json.status == 'pending' + - deactivate_1 is changed + - order_2.output_json.status == 'pending' + - deactivate_2 is changed + - order_3.output_json.status == 'deactivated' + - deactivate_3 is not changed + - order_4.output_json.status == 'deactivated' + - deactivate_4 is not changed + - order_5.output_json.status == 'deactivated' diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases new file mode 100644 index 000000000..b7f6d4f48 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases @@ -0,0 +1,10 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/generic/1 +azp/posix/1 +cloud/acme + +# For some reason connecting to helper containers does not work on the Alpine VMs +skip/alpine diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml new file mode 100644 index 000000000..2e8ad10b8 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +dependencies: + - setup_acme + - setup_remote_tmp_dir diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml new file mode 100644 index 000000000..b30808ed5 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml @@ -0,0 +1,145 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +## SET UP ACCOUNT KEYS ######################################################################## +- block: + - name: Generate account keys + openssl_privatekey: + path: "{{ remote_tmp_dir }}/{{ item.name }}.pem" + type: "{{ item.type }}" + size: "{{ item.size | default(omit) }}" + curve: "{{ item.curve | default(omit) }}" + force: true + loop: "{{ account_keys }}" + + vars: + account_keys: + - name: account-ec256 + type: ECC + curve: secp256r1 +## CREATE ACCOUNTS AND OBTAIN CERTIFICATES #################################################### +- name: Obtain cert 1 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 1 for renewal check + certificate_name: cert-1 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: "DNS:example.com" + subject_alt_name_critical: false + account_key: account-ec256 + challenge: http-01 + modify_account: true + deactivate_authzs: false + force: true + remaining_days: "{{ omit }}" + terms_agreed: true + account_email: "example@example.org" +## OBTAIN CERTIFICATE INFOS ################################################################### +- name: Dump OpenSSL x509 info + command: + cmd: openssl x509 -in {{ remote_tmp_dir }}/cert-1.pem -noout -text +- name: Obtain certificate information + x509_certificate_info: + path: "{{ remote_tmp_dir }}/cert-1.pem" + register: cert_1_info +- name: Read certificate + slurp: + src: '{{ remote_tmp_dir }}/cert-1.pem' + register: slurp_cert_1 +- name: Obtain certificate information (1/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + # Certificate is valid for ~1826 days + register: cert_1_renewal_1 +- name: Obtain certificate information (2/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + # Certificate is valid for ~1826 days + remaining_days: 1000 + remaining_percentage: 0.5 + register: cert_1_renewal_2 +- name: Obtain certificate information (3/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_content: "{{ slurp_cert_1.content | b64decode }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + now: +1800d + # Certificate is valid for ~26 days + register: cert_1_renewal_3 +- name: Obtain certificate information (4/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + now: +1800d + # Certificate is valid for ~26 days + remaining_days: 30 + remaining_percentage: 0.1 + register: cert_1_renewal_4 +- name: Obtain certificate information (5/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + now: +1800d + # Certificate is valid for ~26 days + remaining_days: 30 + remaining_percentage: 0.01 + register: cert_1_renewal_5 +- name: Obtain certificate information (6/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + now: +1800d + # Certificate is valid for ~26 days + remaining_days: 10 + remaining_percentage: 0.03 + register: cert_1_renewal_6 +- name: Obtain certificate information (7/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + now: +1830d + # Certificate is no longer valid + register: cert_1_renewal_7 +- name: Obtain certificate information (8/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + now: +1830d + # Certificate is no longer valid + register: cert_1_renewal_8 +- name: Obtain certificate information (9/9) + acme_certificate_renewal_info: + select_crypto_backend: "{{ select_crypto_backend }}" + certificate_path: "{{ remote_tmp_dir }}/cert-does-not-exist.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + # Certificate is no longer valid + register: cert_1_renewal_9 diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml new file mode 100644 index 000000000..68d47973d --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml @@ -0,0 +1,40 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ remote_tmp_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml new file mode 100644 index 000000000..6882e5339 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml @@ -0,0 +1,159 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +## PRIVATE KEY ################################################################################ +- name: ({{ certgen_title }}) Create cert private key + openssl_privatekey: + path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key" + type: "{{ 'RSA' if key_type == 'rsa' else 'ECC' }}" + size: "{{ rsa_bits if key_type == 'rsa' else omit }}" + curve: >- + {{ omit if key_type == 'rsa' else + 'secp256r1' if key_type == 'ec256' else + 'secp384r1' if key_type == 'ec384' else + 'secp521r1' if key_type == 'ec521' else + 'invalid value for key_type!' }} + passphrase: "{{ certificate_passphrase | default(omit) | default(omit, true) }}" + cipher: "{{ 'auto' if certificate_passphrase | default() else omit }}" + force: true +## CSR ######################################################################################## +- name: ({{ certgen_title }}) Create cert CSR + openssl_csr: + path: "{{ remote_tmp_dir }}/{{ certificate_name }}.csr" + privatekey_path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key" + privatekey_passphrase: "{{ certificate_passphrase | default(omit) | default(omit, true) }}" + subject_alt_name: "{{ subject_alt_name }}" + subject_alt_name_critical: "{{ subject_alt_name_critical }}" + return_content: true + register: csr_result +## ACME STEP 1 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 1 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + account_key: "{{ (remote_tmp_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + account_key_passphrase: "{{ account_key_passphrase | default(omit) | default(omit, true) }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else remote_tmp_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ remote_tmp_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + register: challenge_data +- name: ({{ certgen_title }}) Print challenge data + debug: + var: challenge_data +- name: ({{ certgen_title }}) Create HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: PUT + body_format: raw + body: "{{ item.value['http-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Create DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: PUT + body_format: json + body: "{{ item.value }}" + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (acme_challenge_cert_helper) + acme_challenge_cert_helper: + challenge: tls-alpn-01 + challenge_data: "{{ item.value['tls-alpn-01'] }}" + private_key_src: "{{ remote_tmp_dir }}/{{ certificate_name }}.key" + private_key_passphrase: "{{ certificate_passphrase | default(omit) | default(omit, true) }}" + with_dict: "{{ challenge_data.challenge_data if challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper') else {} }}" + register: tls_alpn_challenges + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Read private key + slurp: + src: '{{ remote_tmp_dir }}/{{ certificate_name }}.key' + register: slurp + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Set TLS ALPN challenges (acme_challenge_cert_helper) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.domain }}/{{ item.identifier }}/certificate-and-key" + method: PUT + body_format: raw + body: "{{ item.challenge_certificate }}\n{{ slurp.content | b64decode }}" + headers: + content-type: "application/pem-certificate-chain" + with_items: "{{ tls_alpn_challenges.results if challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper') else [] }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (der-value-b64) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}/{{ item.value['tls-alpn-01'].resource_original }}/der-value-b64" + method: PUT + body_format: raw + body: "{{ item.value['tls-alpn-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data if challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'der-value-b64') else {} }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'der-value-b64')" +## ACME STEP 2 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 2 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: false + account_key: "{{ (remote_tmp_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + account_key_passphrase: "{{ account_key_passphrase | default(omit) | default(omit, true) }}" + account_uri: "{{ challenge_data.account_uri }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else remote_tmp_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ remote_tmp_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + data: "{{ challenge_data }}" + retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}" + select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}" + register: certificate_obtain_result + when: challenge_data is changed +- name: ({{ certgen_title }}) Deleting HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Deleting DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Deleting TLS ALPN challenges + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01'" +- name: ({{ certgen_title }}) Get root certificate + get_url: + url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}" + dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-root.pem" +############################################################################################### diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml new file mode 100644 index 000000000..116e524c4 --- /dev/null +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml @@ -0,0 +1,47 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Validate results + assert: + that: + - cert_1_renewal_1.should_renew == false + - cert_1_renewal_1.msg == 'The certificate is still valid and no condition was reached' + - cert_1_renewal_1.supports_ari == supports_ari + - cert_1_renewal_1.cert_id is string or not can_have_cert_id + - cert_1_renewal_2.should_renew == false + - cert_1_renewal_2.msg == 'The certificate is still valid and no condition was reached' + - cert_1_renewal_2.supports_ari == supports_ari + - cert_1_renewal_2.cert_id is string or not can_have_cert_id + - cert_1_renewal_3.should_renew == false + - cert_1_renewal_3.msg == 'The certificate is still valid and no condition was reached' + - cert_1_renewal_3.supports_ari == supports_ari + - cert_1_renewal_3.cert_id is string or not can_have_cert_id + - cert_1_renewal_4.should_renew == true + - cert_1_renewal_4.msg == 'The certificate expires in 25 days' + - cert_1_renewal_4.supports_ari == supports_ari + - cert_1_renewal_4.cert_id is string or not can_have_cert_id + - cert_1_renewal_5.should_renew == true + - cert_1_renewal_5.msg == 'The certificate expires in 25 days' + - cert_1_renewal_5.supports_ari == supports_ari + - cert_1_renewal_5.cert_id is string or not can_have_cert_id + - cert_1_renewal_6.should_renew == true + - cert_1_renewal_6.msg.startswith("The remaining percentage 3.0% of the certificate's lifespan was reached on ") + - cert_1_renewal_6.supports_ari == supports_ari + - cert_1_renewal_6.cert_id is string or not can_have_cert_id + - cert_1_renewal_7.should_renew == true + - cert_1_renewal_7.msg == 'The certificate has already expired' + - cert_1_renewal_7.supports_ari == false + - cert_1_renewal_7.cert_id is string or not can_have_cert_id + - cert_1_renewal_8.should_renew == true + - cert_1_renewal_8.msg == 'No certificate was specified' + - cert_1_renewal_8.supports_ari == false + - cert_1_renewal_8.cert_id is not defined + - cert_1_renewal_9.should_renew == true + - cert_1_renewal_9.msg == 'The certificate file does not exist' + - cert_1_renewal_9.supports_ari == false + - cert_1_renewal_9.cert_id is not defined + vars: + can_have_cert_id: cert_1_info.authority_key_identifier is string + supports_ari: false diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml index 4eed1031a..d87501884 100644 --- a/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml +++ b/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml @@ -28,6 +28,7 @@ acme_version: 2 validate_certs: false method: directory-only + select_crypto_backend: "{{ select_crypto_backend }}" register: directory - debug: var=directory @@ -40,6 +41,7 @@ url: "{{ directory.directory.newAccount}}" method: post content: '{"termsOfServiceAgreed":true}' + select_crypto_backend: "{{ select_crypto_backend }}" register: account_creation # account_creation.headers.location contains the account URI # if creation was successful @@ -54,6 +56,7 @@ account_uri: "{{ account_creation.headers.location }}" url: "{{ account_creation.headers.location }}" method: get + select_crypto_backend: "{{ select_crypto_backend }}" register: account_get - debug: var=account_get @@ -67,6 +70,7 @@ url: "{{ account_creation.headers.location }}" method: post content: '{{ account_info | to_json }}' + select_crypto_backend: "{{ select_crypto_backend }}" vars: account_info: # For valid values, see @@ -86,6 +90,7 @@ url: "{{ directory.directory.newOrder }}" method: post content: '{{ create_order | to_json }}' + select_crypto_backend: "{{ select_crypto_backend }}" vars: create_order: # For valid values, see @@ -108,6 +113,7 @@ account_uri: "{{ account_creation.headers.location }}" url: "{{ new_order.headers.location }}" method: get + select_crypto_backend: "{{ select_crypto_backend }}" register: order - debug: var=order @@ -120,6 +126,7 @@ account_uri: "{{ account_creation.headers.location }}" url: "{{ item }}" method: get + select_crypto_backend: "{{ select_crypto_backend }}" loop: "{{ order.output_json.authorizations }}" register: authz - debug: var=authz @@ -133,6 +140,7 @@ account_uri: "{{ account_creation.headers.location }}" url: "{{ (item.challenges | selectattr('type', 'equalto', 'http-01') | list)[0].url }}" method: get + select_crypto_backend: "{{ select_crypto_backend }}" register: http01challenge loop: "{{ authz.results | map(attribute='output_json') | list }}" - debug: var=http01challenge @@ -147,6 +155,7 @@ url: "{{ item.url }}" method: post content: '{}' + select_crypto_backend: "{{ select_crypto_backend }}" register: activation loop: "{{ http01challenge.results | map(attribute='output_json') | list }}" - debug: var=activation @@ -160,6 +169,7 @@ account_uri: "{{ account_creation.headers.location }}" url: "{{ item.url }}" method: get + select_crypto_backend: "{{ select_crypto_backend }}" register: validation_result loop: "{{ http01challenge.results | map(attribute='output_json') | list }}" until: "validation_result.output_json.status not in ['pending', 'processing']" diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml index 99832a517..4bbd818ee 100644 --- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml +++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml @@ -249,10 +249,24 @@ ownca_not_after: 20191023133742Z path: "{{ remote_tmp_dir }}/ownca_cert3.pem" csr_path: "{{ remote_tmp_dir }}/csr.csr" - privatekey_path: "{{ remote_tmp_dir }}/privatekey3.pem" + privatekey_path: "{{ remote_tmp_dir }}/privatekey.pem" + ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter (idempotent) + x509_certificate: + provider: ownca + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + ignore_timestamps: false + path: "{{ remote_tmp_dir }}/ownca_cert3.pem" + csr_path: "{{ remote_tmp_dir }}/csr.csr" + privatekey_path: "{{ remote_tmp_dir }}/privatekey.pem" ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem' select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_cert3_idem - name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with relative notBefore and notAfter x509_certificate: diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml index a0f23643b..eeea25ddd 100644 --- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml +++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml @@ -220,6 +220,18 @@ privatekey_path: "{{ remote_tmp_dir }}/privatekey3.pem" select_crypto_backend: '{{ select_crypto_backend }}' +- name: (Selfsigned, {{select_crypto_backend}}) Create certificate3 with notBefore and notAfter (idempotent) + x509_certificate: + provider: selfsigned + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + ignore_timestamps: false + path: "{{ remote_tmp_dir }}/cert3.pem" + csr_path: "{{ remote_tmp_dir }}/csr3.pem" + privatekey_path: "{{ remote_tmp_dir }}/privatekey3.pem" + select_crypto_backend: '{{ select_crypto_backend }}' + register: cert3_selfsigned_idem + - name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey openssl_privatekey: path: '{{ remote_tmp_dir }}/privatekey_ecc.pem' diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml index ac25b6295..ade7e6f51 100644 --- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml +++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml @@ -98,6 +98,11 @@ that: - ownca_cert3_notAfter.stdout == 'Oct 23 13:37:42 2019' +- name: (OwnCA validation, {{select_crypto_backend}}) Validate idempotency + assert: + that: + - ownca_cert3_idem is not changed + - name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (test - ownca certificate pubkey) shell: '{{ openssl_binary }} x509 -noout -pubkey -in {{ remote_tmp_dir }}/ownca_cert_ecc.pem' register: ownca_cert_ecc_pubkey diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml index c76310437..c7254eb3e 100644 --- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml +++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml @@ -139,6 +139,11 @@ that: - cert3_notAfter.stdout == 'Oct 23 13:37:42 2019' +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate idempotency + assert: + that: + - cert3_selfsigned_idem is not changed + - name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (test - privatekey's pubkey) shell: '{{ openssl_binary }} ec -pubout -in {{ remote_tmp_dir }}/privatekey_ecc.pem' register: privatekey_ecc_pubkey diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py index 988bcdaeb..c4aa09a6a 100644 --- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py @@ -9,8 +9,10 @@ __metaclass__ = type import base64 import datetime import os +import sys from ansible_collections.community.crypto.plugins.module_utils.acme.backends import ( + CertificateInformation, CryptoBackend, ) @@ -79,6 +81,12 @@ TEST_CSRS = [ TEST_CERT = load_fixture("cert_1.pem") +TEST_CERT_2 = load_fixture("cert_2.pem") + + +TEST_CERT_OPENSSL_OUTPUT = load_fixture("cert_1.txt") # OpenSSL 3.3.0 output +TEST_CERT_OPENSSL_OUTPUT_2 = load_fixture("cert_2.txt") # OpenSSL 3.3.0 output +TEST_CERT_OPENSSL_OUTPUT_2B = load_fixture("cert_2-b.txt") # OpenSSL 1.1.1f output TEST_CERT_DAYS = [ @@ -88,6 +96,81 @@ TEST_CERT_DAYS = [ ] +TEST_CERT_INFO = CertificateInformation( + not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24), + not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), + serial_number=1, + subject_key_identifier=b'\x98\xD2\xFD\x3C\xCC\xCD\x69\x45\xFB\xE2\x8C\x30\x2C\x54\x62\x18\x34\xB7\x07\x73', + authority_key_identifier=None, +) + + +TEST_CERT_INFO_2 = CertificateInformation( + not_valid_before=datetime.datetime(2024, 5, 4, 20, 42, 21), + not_valid_after=datetime.datetime(2029, 5, 4, 20, 42, 20), + serial_number=4218235397573492796, + subject_key_identifier=b'\x17\xE5\x83\x22\x14\xEF\x74\xD3\xBE\x7E\x30\x76\x56\x1F\x51\x74\x65\x1F\xE9\xF0', + authority_key_identifier=b'\x13\xC3\x4C\x3E\x59\x45\xDD\xE3\x63\x51\xA3\x46\x80\xC4\x08\xC7\x14\xC0\x64\x4E', +) + + +TEST_CERT_INFO = [ + (TEST_CERT, TEST_CERT_INFO, TEST_CERT_OPENSSL_OUTPUT), + (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2), + (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2B), +] + + +TEST_PARSE_ACME_TIMESTAMP = [ + ( + '2024-01-01T00:11:22Z', + dict(year=2024, month=1, day=1, hour=0, minute=11, second=22), + ), + ( + '2024-01-01T00:11:22.123Z', + dict(year=2024, month=1, day=1, hour=0, minute=11, second=22, microsecond=123000), + ), + ( + '2024-04-17T06:54:13.333333334Z', + dict(year=2024, month=4, day=17, hour=6, minute=54, second=13, microsecond=333333), + ), +] + +if sys.version_info >= (3, 5): + TEST_PARSE_ACME_TIMESTAMP.extend([ + ( + '2024-01-01T00:11:22+0100', + dict(year=2023, month=12, day=31, hour=23, minute=11, second=22), + ), + ( + '2024-01-01T00:11:22.123+0100', + dict(year=2023, month=12, day=31, hour=23, minute=11, second=22, microsecond=123000), + ), + ]) + + +TEST_INTERPOLATE_TIMESTAMP = [ + ( + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + 0.0, + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + ), + ( + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + 0.5, + dict(year=2024, month=1, day=1, hour=0, minute=30, second=0), + ), + ( + dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + 1.0, + dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), + ), +] + + class FakeBackend(CryptoBackend): def parse_key(self, key_file=None, key_content=None, passphrase=None): raise BackendException('Not implemented in fake backend') @@ -98,6 +181,9 @@ class FakeBackend(CryptoBackend): def create_mac_key(self, alg, key): raise BackendException('Not implemented in fake backend') + def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None): + raise BackendException('Not implemented in fake backend') + def get_csr_identifiers(self, csr_filename=None, csr_content=None): raise BackendException('Not implemented in fake backend') @@ -106,3 +192,6 @@ class FakeBackend(CryptoBackend): def create_chain_matcher(self, criterium): raise BackendException('Not implemented in fake backend') + + def get_cert_information(self, cert_filename=None, cert_content=None): + raise BackendException('Not implemented in fake backend') diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt new file mode 100644 index 000000000..e989d914d --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt @@ -0,0 +1,38 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: ecdsa-with-SHA256 + Issuer: CN=ansible.com + Validity + Not Before: Nov 25 15:28:23 2018 GMT + Not After : Nov 26 15:28:24 2018 GMT + Subject: CN=ansible.com + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:00:9c:f4:c8:00:17:03:01:26:3a:14:d1:92:35: + f1:c2:07:9d:6d:63:ba:82:86:d8:33:79:56:b3:3a: + d2:eb:c1:bc:41:2c:e1:5d:1e:80:99:0d:c8:cd:90: + e2:9a:74:d3:5c:ee:d7:85:5c:a5:0d:3f:12:2f:31: + 38:e3:f1:29:9b + ASN1 OID: prime256v1 + NIST CURVE: P-256 + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:example.com, DNS:example.org + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Key Usage: critical + Digital Signature + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Subject Key Identifier: + 98:D2:FD:3C:CC:CD:69:45:FB:E2:8C:30:2C:54:62:18:34:B7:07:73 + Signature Algorithm: ecdsa-with-SHA256 + Signature Value: + 30:46:02:21:00:bc:fb:52:bf:7a:93:2d:0e:7c:ce:43:f4:cc: + 05:98:28:36:8d:c7:2a:9b:f5:20:94:62:3d:fb:82:9e:38:42: + 32:02:21:00:c0:55:f8:b5:d9:65:41:2a:dd:d4:76:3f:8c:cb: + 07:c1:d2:b9:c0:7d:c9:90:af:fd:f9:f1:b0:c9:13:f5:d5:52 diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt new file mode 100644 index 000000000..78326443b --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt @@ -0,0 +1,57 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4218235397573492796 (0x3a8a2ebeb358c03c) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN = Pebble Intermediate CA 734609 + Validity + Not Before: May 4 20:42:21 2024 GMT + Not After : May 4 20:42:20 2029 GMT + Subject: CN = example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (1024 bit) + Modulus: + 00:c1:43:a5:f9:ad:00:b7:bb:1b:73:27:00:b3:a2: + 4e:27:0d:ff:ae:64:3e:a0:7e:f9:28:56:48:47:21: + 9e:0f:d8:fb:69:b5:21:e8:98:84:60:6c:aa:73:b9: + 6e:d9:f6:19:ad:85:e0:c2:f6:80:d3:22:b8:5a:d6: + 3a:89:3e:2a:7a:fc:1d:bf:fc:69:20:e5:91:b8:34: + 52:26:c8:15:74:e1:36:0c:cd:ab:01:4a:ad:83:f5: + 0b:77:96:31:cf:1c:ea:6f:88:75:23:ac:51:a6:d8: + 77:43:1b:b3:44:93:2c:8d:05:25:fb:77:41:36:94: + 81:d5:ca:56:ff:b5:23:b2:a5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 17:E5:83:22:14:EF:74:D3:BE:7E:30:76:56:1F:51:74:65:1F:E9:F0 + X509v3 Authority Key Identifier: + keyid:13:C3:4C:3E:59:45:DD:E3:63:51:A3:46:80:C4:08:C7:14:C0:64:4E + + Authority Information Access: + OCSP - URI:http://10.88.0.74:5000/ocsp + + X509v3 Subject Alternative Name: + DNS:example.com + Signature Algorithm: sha256WithRSAEncryption + 31:43:de:b6:48:f4:b8:30:46:25:65:e6:91:22:33:1b:d1:ba: + 3f:60:f8:c3:18:32:72:e9:f8:d1:88:11:5a:0a:86:dc:1d:6d: + a5:ea:58:cd:05:ea:cd:5e:40:86:c1:ae:d5:cd:2e:8a:ca:50: + ee:df:bd:cf:6c:d9:20:3b:4b:49:f8:d5:8a:e3:be:f3:dd:24: + b2:7f:3f:3b:bf:e6:8d:7a:f8:8f:4b:6e:25:60:80:33:6f:0f: + 53:b7:7d:94:2a:d2:4a:db:3a:2f:70:79:d7:bf:05:ed:df:10: + 61:e7:24:ac:b2:fc:03:bd:ad:8c:e1:f3:1d:cc:78:99:e3:22: + 59:bf:c5:92:57:95:92:56:35:fc:05:8b:26:10:c5:1b:87:17: + 64:0b:bd:33:a9:54:d5:c0:2b:43:56:1b:52:d3:4f:8b:6f:25: + 06:58:7f:6f:aa:27:35:05:d5:57:6d:83:a0:73:de:40:3f:67: + 1c:5a:92:c6:37:e6:8f:c7:b8:91:d7:50:b9:4d:d4:f2:92:1f: + 8b:93:0c:e2:b4:b8:d7:1d:8e:ce:6d:19:dc:8f:12:8e:c0:f2: + 92:3b:95:5a:8c:c8:69:0e:0b:f7:fa:1f:55:62:80:7c:e2:f6: + 41:3f:7d:69:36:9e:7c:90:7e:d7:3b:e6:a3:15:de:a4:7d:95: + 13:46:c6:1a diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem new file mode 100644 index 000000000..92aecb621 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDDjCCAfagAwIBAgIIOoouvrNYwDwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE +AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA3MzQ2MDkwHhcNMjQwNTA0MjA0MjIx +WhcNMjkwNTA0MjA0MjIwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAwUOl+a0At7sbcycAs6JOJw3/rmQ+oH75KFZI +RyGeD9j7abUh6JiEYGyqc7lu2fYZrYXgwvaA0yK4WtY6iT4qevwdv/xpIOWRuDRS +JsgVdOE2DM2rAUqtg/ULd5Yxzxzqb4h1I6xRpth3QxuzRJMsjQUl+3dBNpSB1cpW +/7UjsqUCAwEAAaOB0TCBzjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBflgyIU73TT +vn4wdlYfUXRlH+nwMB8GA1UdIwQYMBaAFBPDTD5ZRd3jY1GjRoDECMcUwGROMDcG +CCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovLzEwLjg4LjAuNzQ6NTAw +MC9vY3NwMBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQAxQ962SPS4MEYlZeaRIjMb0bo/YPjDGDJy6fjRiBFaCobcHW2l6ljNBerNXkCG +wa7VzS6KylDu373PbNkgO0tJ+NWK477z3SSyfz87v+aNeviPS24lYIAzbw9Tt32U +KtJK2zovcHnXvwXt3xBh5ySssvwDva2M4fMdzHiZ4yJZv8WSV5WSVjX8BYsmEMUb +hxdkC70zqVTVwCtDVhtS00+LbyUGWH9vqic1BdVXbYOgc95AP2ccWpLGN+aPx7iR +11C5TdTykh+LkwzitLjXHY7ObRncjxKOwPKSO5VajMhpDgv3+h9VYoB84vZBP31p +Np58kH7XO+ajFd6kfZUTRsYa +-----END CERTIFICATE----- diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt new file mode 100644 index 000000000..3cda74955 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt @@ -0,0 +1,56 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4218235397573492796 (0x3a8a2ebeb358c03c) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Pebble Intermediate CA 734609 + Validity + Not Before: May 4 20:42:21 2024 GMT + Not After : May 4 20:42:20 2029 GMT + Subject: CN=example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:c1:43:a5:f9:ad:00:b7:bb:1b:73:27:00:b3:a2: + 4e:27:0d:ff:ae:64:3e:a0:7e:f9:28:56:48:47:21: + 9e:0f:d8:fb:69:b5:21:e8:98:84:60:6c:aa:73:b9: + 6e:d9:f6:19:ad:85:e0:c2:f6:80:d3:22:b8:5a:d6: + 3a:89:3e:2a:7a:fc:1d:bf:fc:69:20:e5:91:b8:34: + 52:26:c8:15:74:e1:36:0c:cd:ab:01:4a:ad:83:f5: + 0b:77:96:31:cf:1c:ea:6f:88:75:23:ac:51:a6:d8: + 77:43:1b:b3:44:93:2c:8d:05:25:fb:77:41:36:94: + 81:d5:ca:56:ff:b5:23:b2:a5 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 17:E5:83:22:14:EF:74:D3:BE:7E:30:76:56:1F:51:74:65:1F:E9:F0 + X509v3 Authority Key Identifier: + 13:C3:4C:3E:59:45:DD:E3:63:51:A3:46:80:C4:08:C7:14:C0:64:4E + Authority Information Access: + OCSP - URI:http://10.88.0.74:5000/ocsp + X509v3 Subject Alternative Name: + DNS:example.com + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 31:43:de:b6:48:f4:b8:30:46:25:65:e6:91:22:33:1b:d1:ba: + 3f:60:f8:c3:18:32:72:e9:f8:d1:88:11:5a:0a:86:dc:1d:6d: + a5:ea:58:cd:05:ea:cd:5e:40:86:c1:ae:d5:cd:2e:8a:ca:50: + ee:df:bd:cf:6c:d9:20:3b:4b:49:f8:d5:8a:e3:be:f3:dd:24: + b2:7f:3f:3b:bf:e6:8d:7a:f8:8f:4b:6e:25:60:80:33:6f:0f: + 53:b7:7d:94:2a:d2:4a:db:3a:2f:70:79:d7:bf:05:ed:df:10: + 61:e7:24:ac:b2:fc:03:bd:ad:8c:e1:f3:1d:cc:78:99:e3:22: + 59:bf:c5:92:57:95:92:56:35:fc:05:8b:26:10:c5:1b:87:17: + 64:0b:bd:33:a9:54:d5:c0:2b:43:56:1b:52:d3:4f:8b:6f:25: + 06:58:7f:6f:aa:27:35:05:d5:57:6d:83:a0:73:de:40:3f:67: + 1c:5a:92:c6:37:e6:8f:c7:b8:91:d7:50:b9:4d:d4:f2:92:1f: + 8b:93:0c:e2:b4:b8:d7:1d:8e:ce:6d:19:dc:8f:12:8e:c0:f2: + 92:3b:95:5a:8c:c8:69:0e:0b:f7:fa:1f:55:62:80:7c:e2:f6: + 41:3f:7d:69:36:9e:7c:90:7e:d7:3b:e6:a3:15:de:a4:7d:95: + 13:46:c6:1a diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license new file mode 100644 index 000000000..edff8c768 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license @@ -0,0 +1,3 @@ +GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +SPDX-License-Identifier: GPL-3.0-or-later +SPDX-FileCopyrightText: Ansible Project diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py index 59da68a3b..9186e2430 100644 --- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py @@ -16,11 +16,22 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryp CryptographyBackend, ) +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + ensure_utc_timezone, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + CRYPTOGRAPHY_TIMEZONE, +) + from .backend_data import ( TEST_KEYS, TEST_CSRS, TEST_CERT, TEST_CERT_DAYS, + TEST_CERT_INFO, + TEST_PARSE_ACME_TIMESTAMP, + TEST_INTERPOLATE_TIMESTAMP, ) @@ -64,3 +75,49 @@ def test_certdays_cryptography(now, expected_days, tmpdir): assert days == expected_days days = backend.get_cert_days(cert_content=TEST_CERT, now=now) assert days == expected_days + + +@pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO) +def test_get_cert_information(cert_content, expected_cert_info, openssl_output, tmpdir): + fn = tmpdir / 'test-cert.pem' + fn.write(cert_content) + module = MagicMock() + backend = CryptographyBackend(module) + + if CRYPTOGRAPHY_TIMEZONE: + expected_cert_info = expected_cert_info._replace( + not_valid_after=ensure_utc_timezone(expected_cert_info.not_valid_after), + not_valid_before=ensure_utc_timezone(expected_cert_info.not_valid_before), + ) + + cert_info = backend.get_cert_information(cert_filename=str(fn)) + assert cert_info == expected_cert_info + cert_info = backend.get_cert_information(cert_content=cert_content) + assert cert_info == expected_cert_info + + +def test_now(): + module = MagicMock() + backend = CryptographyBackend(module) + now = backend.get_now() + assert CRYPTOGRAPHY_TIMEZONE == (now.tzinfo is not None) + + +@pytest.mark.parametrize("input, expected", TEST_PARSE_ACME_TIMESTAMP) +def test_parse_acme_timestamp(input, expected): + module = MagicMock() + backend = CryptographyBackend(module) + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.parse_acme_timestamp(input) + assert ts_expected == timestamp + + +@pytest.mark.parametrize("start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) +def test_interpolate_timestamp(start, end, percentage, expected): + module = MagicMock() + backend = CryptographyBackend(module) + ts_start = backend.get_utc_datetime(**start) + ts_end = backend.get_utc_datetime(**end) + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage) + assert ts_expected == timestamp diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py index dd30cf795..5138a6202 100644 --- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py @@ -18,6 +18,12 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.backend_open from .backend_data import ( TEST_KEYS, TEST_CSRS, + TEST_CERT, + TEST_CERT_OPENSSL_OUTPUT, + TEST_CERT_DAYS, + TEST_CERT_INFO, + TEST_PARSE_ACME_TIMESTAMP, + TEST_INTERPOLATE_TIMESTAMP, ) @@ -61,3 +67,56 @@ def test_normalize_ip(ip, result): module = MagicMock() backend = OpenSSLCLIBackend(module, openssl_binary='openssl') assert backend._normalize_ip(ip) == result + + +@pytest.mark.parametrize("now, expected_days", TEST_CERT_DAYS) +def test_certdays_cryptography(now, expected_days, tmpdir): + fn = tmpdir / 'test-cert.pem' + fn.write(TEST_CERT) + module = MagicMock() + module.run_command = MagicMock(return_value=(0, TEST_CERT_OPENSSL_OUTPUT, 0)) + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + days = backend.get_cert_days(cert_filename=str(fn), now=now) + assert days == expected_days + days = backend.get_cert_days(cert_content=TEST_CERT, now=now) + assert days == expected_days + + +@pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO) +def test_get_cert_information(cert_content, expected_cert_info, openssl_output, tmpdir): + fn = tmpdir / 'test-cert.pem' + fn.write(cert_content) + module = MagicMock() + module.run_command = MagicMock(return_value=(0, openssl_output, 0)) + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + cert_info = backend.get_cert_information(cert_filename=str(fn)) + assert cert_info == expected_cert_info + cert_info = backend.get_cert_information(cert_content=cert_content) + assert cert_info == expected_cert_info + + +def test_now(): + module = MagicMock() + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + now = backend.get_now() + assert now.tzinfo is None + + +@pytest.mark.parametrize("input, expected", TEST_PARSE_ACME_TIMESTAMP) +def test_parse_acme_timestamp(input, expected): + module = MagicMock() + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.parse_acme_timestamp(input) + assert ts_expected == timestamp + + +@pytest.mark.parametrize("start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) +def test_interpolate_timestamp(start, end, percentage, expected): + module = MagicMock() + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + ts_start = backend.get_utc_datetime(**start) + ts_end = backend.get_utc_datetime(**end) + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage) + assert ts_expected == timestamp diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py index 9bdd8eb6e..5cc318ac2 100644 --- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py @@ -6,12 +6,20 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import datetime + import pytest +from ansible_collections.community.crypto.plugins.module_utils.acme.backends import ( + CertificateInformation, +) from ansible_collections.community.crypto.plugins.module_utils.acme.utils import ( nopad_b64, pem_to_der, + process_links, + parse_retry_after, + compute_cert_id, ) from .backend_data import ( @@ -27,6 +35,73 @@ NOPAD_B64 = [ ] +TEST_LINKS_HEADER = [ + ( + {}, + [], + ), + ( + { + 'link': '<foo>; rel="bar"' + }, + [ + ('foo', 'bar'), + ], + ), + ( + { + 'link': '<foo>; rel="bar", <baz>; rel="bam"' + }, + [ + ('foo', 'bar'), + ('baz', 'bam'), + ], + ), + ( + { + 'link': '<https://one.example.com>; rel="preconnect", <https://two.example.com>; rel="preconnect", <https://three.example.com>; rel="preconnect"' + }, + [ + ('https://one.example.com', 'preconnect'), + ('https://two.example.com', 'preconnect'), + ('https://three.example.com', 'preconnect'), + ], + ), +] + + +TEST_RETRY_AFTER_HEADER = [ + ('120', datetime.datetime(2024, 4, 29, 0, 2, 0)), + ('Wed, 21 Oct 2015 07:28:00 GMT', datetime.datetime(2015, 10, 21, 7, 28, 0)), +] + + +TEST_COMPUTE_CERT_ID = [ + ( + CertificateInformation( + not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24), + not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), + serial_number=1, + subject_key_identifier=None, + authority_key_identifier=b'\x00\xff', + ), + 'AP8.AQ', + ), + ( + # AKI, serial number, and expected result taken from + # https://letsencrypt.org/2024/04/25/guide-to-integrating-ari-into-existing-acme-clients.html#step-3-constructing-the-ari-certid + CertificateInformation( + not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24), + not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23), + serial_number=0x87654321, + subject_key_identifier=None, + authority_key_identifier=b'\x69\x88\x5B\x6B\x87\x46\x40\x41\xE1\xB3\x7B\x84\x7B\xA0\xAE\x2C\xDE\x01\xC8\xD4', + ), + 'aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE', + ), +] + + @pytest.mark.parametrize("value, result", NOPAD_B64) def test_nopad_b64(value, result): assert nopad_b64(value.encode('utf-8')) == result @@ -37,3 +112,25 @@ def test_pem_to_der(pem, der, tmpdir): fn = tmpdir / 'test.pem' fn.write(pem) assert pem_to_der(str(fn)) == der + + +@pytest.mark.parametrize("value, expected_result", TEST_LINKS_HEADER) +def test_process_links(value, expected_result): + data = [] + + def callback(url, rel): + data.append((url, rel)) + + process_links(value, callback) + + assert expected_result == data + + +@pytest.mark.parametrize("value, expected_result", TEST_RETRY_AFTER_HEADER) +def test_parse_retry_after(value, expected_result): + assert expected_result == parse_retry_after(value, now=datetime.datetime(2024, 4, 29, 0, 0, 0)) + + +@pytest.mark.parametrize("cert_info, expected_result", TEST_COMPUTE_CERT_ID) +def test_compute_cert_id(cert_info, expected_result): + assert expected_result == compute_cert_id(backend=None, cert_info=cert_info) diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py new file mode 100644 index 000000000..4fd917713 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import pytest + +from ansible_collections.community.crypto.plugins.module_utils.crypto.math import ( + binary_exp_mod, + simple_gcd, + quick_is_not_prime, + convert_int_to_bytes, + convert_int_to_hex, + convert_bytes_to_int, +) + + +@pytest.mark.parametrize('f, e, m, result', [ + (0, 0, 5, 1), + (0, 1, 5, 0), + (2, 1, 5, 2), + (2, 2, 5, 4), + (2, 3, 5, 3), + (2, 10, 5, 4), +]) +def test_binary_exp_mod(f, e, m, result): + value = binary_exp_mod(f, e, m) + print(value) + assert value == result + + +@pytest.mark.parametrize('a, b, result', [ + (0, -123, -123), + (0, 123, 123), + (-123, 0, -123), + (123, 0, 123), + (-123, 1, 1), + (123, 1, 1), + (1, -123, -1), + (1, 123, 1), + (1024, 10, 2), +]) +def test_simple_gcd(a, b, result): + value = simple_gcd(a, b) + print(value) + assert value == result + + +@pytest.mark.parametrize('n, result', [ + (-2, True), + (0, True), + (1, True), + (2, False), + (3, False), + (4, True), + (5, False), + (6, True), + (7, False), + (8, True), + (9, True), + (10, True), + (211, False), # the smallest prime number >= 200 +]) +def test_quick_is_not_prime(n, result): + value = quick_is_not_prime(n) + print(value) + assert value == result + + +@pytest.mark.parametrize('no, count, result', [ + (0, None, b''), + (0, 1, b'\x00'), + (0, 2, b'\x00\x00'), + (1, None, b'\x01'), + (1, 2, b'\x00\x01'), + (255, None, b'\xff'), + (256, None, b'\x01\x00'), +]) +def test_convert_int_to_bytes(no, count, result): + value = convert_int_to_bytes(no, count=count) + print(value) + assert value == result + + +@pytest.mark.parametrize('no, digits, result', [ + (0, None, '0'), + (1, None, '1'), + (16, None, '10'), + (1, 3, '001'), + (255, None, 'ff'), + (256, None, '100'), + (256, 2, '100'), + (256, 3, '100'), + (256, 4, '0100'), +]) +def test_convert_int_to_hex(no, digits, result): + value = convert_int_to_hex(no, digits=digits) + print(value) + assert value == result + + +@pytest.mark.parametrize('data, result', [ + (b'', 0), + (b'\x00', 0), + (b'\x00\x01', 1), + (b'\x01', 1), + (b'\xff', 255), + (b'\x01\x00', 256), +]) +def test_convert_bytes_to_int(data, result): + value = convert_bytes_to_int(data) + print(value) + assert value == result diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py new file mode 100644 index 000000000..35a83f4e4 --- /dev/null +++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py @@ -0,0 +1,323 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import datetime +import sys + +import pytest + + +from ansible_collections.community.crypto.plugins.module_utils.time import ( + add_or_remove_timezone, + get_now_datetime, + convert_relative_to_datetime, + ensure_utc_timezone, + from_epoch_seconds, + get_epoch_seconds, + get_relative_time_option, + remove_timezone, + UTC, +) + + +TEST_REMOVE_TIMEZONE = [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 1, 2), + ), + ( + datetime.datetime(2024, 1, 1, 0, 1, 2), + datetime.datetime(2024, 1, 1, 0, 1, 2), + ), +] + +TEST_UTC_TIMEZONE = [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2), + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + ), + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + ), +] + +TEST_EPOCH_SECONDS = [ + (0, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=0)), + (1E-6, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1)), + (1E-3, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1000)), + (3691.2, dict(year=1970, day=1, month=1, hour=1, minute=1, second=31, microsecond=200000)), +] + +TEST_EPOCH_TO_SECONDS = [ + (datetime.datetime(1970, 1, 1, 0, 1, 2, 0), 62), + (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=UTC), 62), +] + +TEST_CONVERT_RELATIVE_TO_DATETIME = [ + ( + '+0', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 1, 0, 0, 0), + ), + ( + '+1s', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 0, 1), + ), + ( + '-10w20d30h40m50s', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2023, 10, 1, 17, 19, 10), + ), + ( + '+0', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + ), + ( + '+1s', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 0, 1, tzinfo=UTC), + ), + ( + '-10w20d30h40m50s', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2023, 10, 1, 17, 19, 10, tzinfo=UTC), + ), +] + +TEST_GET_RELATIVE_TIME_OPTION = [ + ( + '+1d2h3m4s', + 'foo', + 'cryptography', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 2, 3, 4), + ), + ( + '-1w10d24h', + 'foo', + 'cryptography', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2023, 12, 14, 0, 0, 0), + ), + ( + '20240102040506Z', + 'foo', + 'cryptography', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 6), + ), + ( + '202401020405Z', + 'foo', + 'cryptography', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 0), + ), + ( + '+1d2h3m4s', + 'foo', + 'cryptography', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 2, 3, 4, tzinfo=UTC), + ), + ( + '-1w10d24h', + 'foo', + 'cryptography', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2023, 12, 14, 0, 0, 0, tzinfo=UTC), + ), + ( + '20240102040506Z', + 'foo', + 'cryptography', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 6, tzinfo=UTC), + ), + ( + '202401020405Z', + 'foo', + 'cryptography', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 4, 5, 0, tzinfo=UTC), + ), + ( + '+1d2h3m4s', + 'foo', + 'pyopenssl', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + '20240102020304Z', + ), + ( + '-1w10d24h', + 'foo', + 'pyopenssl', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + '20231214000000Z', + ), + ( + '20240102040506Z', + 'foo', + 'pyopenssl', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + '20240102040506Z', + ), + ( + '202401020405Z', + 'foo', + 'pyopenssl', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + '202401020405Z', + ), +] + + +if sys.version_info >= (3, 5): + ONE_HOUR_PLUS = datetime.timezone(datetime.timedelta(hours=1)) + + TEST_REMOVE_TIMEZONE.extend([ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), + datetime.datetime(2023, 12, 31, 23, 1, 2), + ), + ]) + TEST_UTC_TIMEZONE.extend([ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), + datetime.datetime(2023, 12, 31, 23, 1, 2, tzinfo=UTC), + ), + ]) + TEST_EPOCH_TO_SECONDS.extend([ + (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=ONE_HOUR_PLUS), 62 - 3600), + ]) + TEST_GET_RELATIVE_TIME_OPTION.extend([ + ( + '20240102040506+0100', + 'foo', + 'cryptography', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 6), + ), + ( + '202401020405+0100', + 'foo', + 'cryptography', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 0), + ), + ( + '20240102040506+0100', + 'foo', + 'cryptography', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 6, tzinfo=UTC), + ), + ( + '202401020405+0100', + 'foo', + 'cryptography', + True, + datetime.datetime(2024, 1, 1, 0, 0, 0), + datetime.datetime(2024, 1, 2, 3, 5, 0, tzinfo=UTC), + ), + ( + '20240102040506+0100', + 'foo', + 'pyopenssl', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + '20240102040506+0100', + ), + ( + '202401020405+0100', + 'foo', + 'pyopenssl', + False, + datetime.datetime(2024, 1, 1, 0, 0, 0), + '202401020405+0100', + ), + ]) + + +@pytest.mark.parametrize("input, expected", TEST_REMOVE_TIMEZONE) +def test_remove_timezone(input, expected): + output_1 = remove_timezone(input) + assert expected == output_1 + output_2 = add_or_remove_timezone(input, with_timezone=False) + assert expected == output_2 + + +@pytest.mark.parametrize("input, expected", TEST_UTC_TIMEZONE) +def test_utc_timezone(input, expected): + output_1 = ensure_utc_timezone(input) + assert expected == output_1 + output_2 = add_or_remove_timezone(input, with_timezone=True) + assert expected == output_2 + + +def test_get_now_datetime(): + output_1 = get_now_datetime(with_timezone=False) + assert output_1.tzinfo is None + output_2 = get_now_datetime(with_timezone=True) + assert output_2.tzinfo is not None + assert output_2.tzinfo == UTC + + +@pytest.mark.parametrize("seconds, timestamp", TEST_EPOCH_SECONDS) +def test_epoch_seconds(seconds, timestamp): + ts_wo_tz = datetime.datetime(**timestamp) + assert seconds == get_epoch_seconds(ts_wo_tz) + timestamp_w_tz = dict(timestamp) + timestamp_w_tz['tzinfo'] = UTC + ts_w_tz = datetime.datetime(**timestamp_w_tz) + assert seconds == get_epoch_seconds(ts_w_tz) + output_1 = from_epoch_seconds(seconds, with_timezone=False) + assert ts_wo_tz == output_1 + output_2 = from_epoch_seconds(seconds, with_timezone=True) + assert ts_w_tz == output_2 + + +@pytest.mark.parametrize("timestamp, expected_seconds", TEST_EPOCH_TO_SECONDS) +def test_epoch_to_seconds(timestamp, expected_seconds): + assert expected_seconds == get_epoch_seconds(timestamp) + + +@pytest.mark.parametrize("relative_time_string, with_timezone, now, expected", TEST_CONVERT_RELATIVE_TO_DATETIME) +def test_convert_relative_to_datetime(relative_time_string, with_timezone, now, expected): + output = convert_relative_to_datetime(relative_time_string, with_timezone=with_timezone, now=now) + assert expected == output + + +@pytest.mark.parametrize("input_string, input_name, backend, with_timezone, now, expected", TEST_GET_RELATIVE_TIME_OPTION) +def test_get_relative_time_option(input_string, input_name, backend, with_timezone, now, expected): + output = get_relative_time_option(input_string, input_name, backend=backend, with_timezone=with_timezone, now=now) + assert expected == output |